forked from eatvio/eatvio_app
Merge remote-tracking branch 'upstream/main'
This commit is contained in:
10
assets/icons/add_step_photo.svg
Normal file
10
assets/icons/add_step_photo.svg
Normal file
File diff suppressed because one or more lines are too long
|
After Width: | Height: | Size: 14 KiB |
10
assets/icons/upload_photo_of_portion.svg
Normal file
10
assets/icons/upload_photo_of_portion.svg
Normal file
File diff suppressed because one or more lines are too long
|
After Width: | Height: | Size: 21 KiB |
@@ -31,7 +31,6 @@ import 'package:eatvio_app/features/profile/presentation/pages/profile_screen.da
|
||||
import 'package:eatvio_app/features/profile/presentation/pages/subs_screen.dart';
|
||||
import 'package:eatvio_app/features/recipes/presentation/pages/about_recipes_screen.dart';
|
||||
import 'package:eatvio_app/features/recipes/presentation/pages/recipe_create_screen.dart';
|
||||
import 'package:eatvio_app/features/recipes/presentation/pages/recipe_edit_screen.dart';
|
||||
import 'package:eatvio_app/features/registration/presentation/pages/registration_screen.dart';
|
||||
import 'package:eatvio_app/features/settings/entities/settings_entity.dart';
|
||||
import 'package:eatvio_app/features/settings/presentation/pages/change_email_screen.dart';
|
||||
@@ -135,13 +134,6 @@ class AppRouter {
|
||||
name: 'recipeCreateScreen',
|
||||
builder: (context, state) => RecipeCreateScreen(),
|
||||
),
|
||||
GoRoute(
|
||||
path: '/recipe-edit-screen',
|
||||
name: 'recipeEditScreen',
|
||||
builder: (context, state) => RecipeEditScreen(
|
||||
recipeID: state.extra as String,
|
||||
),
|
||||
),
|
||||
GoRoute(
|
||||
path: '/profile-screen',
|
||||
name: 'profileScreen',
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
const String baseUrl = 'https://dev2.eatvio.com';
|
||||
String baseUrl = 'https://dev2.eatvio.com';
|
||||
|
||||
const List<int> durationValues = [7, 14, 21];
|
||||
|
||||
|
||||
@@ -52,7 +52,7 @@ import 'package:eatvio_app/features/profile/presentation/bloc/profile_cubit/prof
|
||||
import 'package:eatvio_app/features/profile/presentation/bloc/sub_cubit/sub_cubit.dart';
|
||||
import 'package:eatvio_app/features/profile/repositories/profile_repository.dart';
|
||||
import 'package:eatvio_app/features/profile/services/profile_service.dart';
|
||||
import 'package:eatvio_app/features/recipes/presentation/bloc/recipe_create_cubit.dart';
|
||||
import 'package:eatvio_app/features/recipes/presentation/bloc/create_recipe_cubit/create_recipe_cubit.dart';
|
||||
import 'package:eatvio_app/features/recipes/reporitories/recipe_repository.dart';
|
||||
import 'package:eatvio_app/features/recipes/services/recipe_service.dart';
|
||||
import 'package:eatvio_app/features/registration/presentation/bloc/registration_cubit/registration_cubit.dart';
|
||||
@@ -81,7 +81,6 @@ Future<void> init() async {
|
||||
sl.registerFactory<CalendarCubit>(() => CalendarCubit(calendarRepository: sl()));
|
||||
sl.registerFactory<ProfileCubit>(() => ProfileCubit(profileRepository: sl(), chatRepository: sl()));
|
||||
sl.registerFactory<profilePlans.PlansCubit>(() => profilePlans.PlansCubit(plansRepository: sl()));
|
||||
sl.registerFactory<RecipeCreateCubit>(() => RecipeCreateCubit(recipeRepository: sl()));
|
||||
sl.registerFactory<DietsCubit>(() => DietsCubit(feedRepository: sl(), dietsRepository: sl()));
|
||||
sl.registerFactory<CommentsCubit>(() => CommentsCubit(feedRepository: sl()));
|
||||
sl.registerFactory<ChatSelectCubit>(() => ChatSelectCubit(chatRepository: sl()));
|
||||
@@ -96,6 +95,7 @@ Future<void> init() async {
|
||||
sl.registerFactory<CreatePlanCubit>(() => CreatePlanCubit());
|
||||
sl.registerFactory<FeedProfileCubit>(() => FeedProfileCubit(profileRepository: sl()));
|
||||
sl.registerFactory<AboutPlanCubit>(() => AboutPlanCubit(planRepository: sl()));
|
||||
sl.registerFactory<CreateRecipeCubit>(() => CreateRecipeCubit());
|
||||
sl.registerFactory<MyDietsCubit>(() => MyDietsCubit(myDietsRepository: sl()));
|
||||
sl.registerFactory<MyPlansCubit>(() => MyPlansCubit(myPlansRepository: sl()));
|
||||
|
||||
|
||||
@@ -64,11 +64,6 @@ class CustomDropDown extends StatelessWidget {
|
||||
value: value,
|
||||
isDense: true,
|
||||
isExpanded: true,
|
||||
style: TextStyle(
|
||||
fontSize: 16,
|
||||
color: Colors.black,
|
||||
fontWeight: FontWeight.w400,
|
||||
),
|
||||
items: items?.map((SelectionPopupModel item) {
|
||||
return DropdownMenuItem<SelectionPopupModel>(
|
||||
value: item,
|
||||
|
||||
@@ -38,10 +38,12 @@ class CustomTextFormField extends StatelessWidget {
|
||||
this.expands = false,
|
||||
this.textAlignVertical,
|
||||
this.counter = const SizedBox.shrink(),
|
||||
this.enabled = true,
|
||||
super.key,
|
||||
this.hasError = false,
|
||||
this.readOnly = false,
|
||||
});
|
||||
|
||||
final Widget? counter;
|
||||
final Alignment? alignment;
|
||||
final double? width;
|
||||
@@ -77,12 +79,15 @@ class CustomTextFormField extends StatelessWidget {
|
||||
final bool expands;
|
||||
final TextAlignVertical? textAlignVertical;
|
||||
final bool hasError;
|
||||
final bool enabled;
|
||||
|
||||
final bool readOnly;
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Align(
|
||||
alignment: alignment ?? Alignment.center,
|
||||
child: TextFormField(
|
||||
enabled: enabled,
|
||||
readOnly: readOnly,
|
||||
textAlignVertical: textAlignVertical,
|
||||
expands: expands,
|
||||
@@ -114,6 +119,7 @@ class CustomTextFormField extends StatelessWidget {
|
||||
prefixIcon: prefix,
|
||||
prefixIconConstraints: prefixConstraints,
|
||||
suffixIcon: suffix,
|
||||
floatingLabelBehavior: FloatingLabelBehavior.always,
|
||||
suffixStyle: TextStyle(
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.w500,
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import 'dart:convert';
|
||||
|
||||
import 'package:dio/dio.dart';
|
||||
import 'package:eatvio_app/core/const.dart';
|
||||
import 'package:eatvio_app/core/locator_service.dart';
|
||||
import 'package:eatvio_app/core/token_storage.dart';
|
||||
@@ -41,6 +42,7 @@ class AuthScreen extends StatefulWidget {
|
||||
class _AuthScreenState extends State<AuthScreen> {
|
||||
bool checkboxState = false;
|
||||
bool hidePassword = true;
|
||||
final baseUrlController = TextEditingController();
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
@@ -49,10 +51,54 @@ class _AuthScreenState extends State<AuthScreen> {
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
centerTitle: true,
|
||||
title: SvgPicture.asset(
|
||||
ImageConstant.imgLogoEatvio,
|
||||
height: 48,
|
||||
fit: BoxFit.contain,
|
||||
title: GestureDetector(
|
||||
onLongPress: () {
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (context) => AlertDialog(
|
||||
title: Text('Base url без https:// и /api'),
|
||||
content: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
CustomTextFormField(
|
||||
controller: baseUrlController,
|
||||
hintText: 'dev2.eatvio.com',
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
InkWell(
|
||||
onTap: () async {
|
||||
baseUrl = 'https://${baseUrlController.text}';
|
||||
sl<Dio>().options.baseUrl = baseUrl;
|
||||
},
|
||||
borderRadius: BorderRadius.circular(20),
|
||||
child: Ink(
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(20),
|
||||
color: appTheme.blueA400,
|
||||
),
|
||||
child: Container(
|
||||
padding: EdgeInsets.symmetric(horizontal: 16, vertical: 12),
|
||||
alignment: Alignment.center,
|
||||
child: Text(
|
||||
'Сохранить',
|
||||
style: TextStyle(
|
||||
color: Colors.white,
|
||||
fontWeight: FontWeight.w600,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
child: SvgPicture.asset(
|
||||
ImageConstant.imgLogoEatvio,
|
||||
height: 48,
|
||||
fit: BoxFit.contain,
|
||||
),
|
||||
),
|
||||
),
|
||||
body: SafeArea(
|
||||
|
||||
@@ -35,6 +35,7 @@ class _DiaryScreenState extends State<DiaryScreen> {
|
||||
CalendarWidget(),
|
||||
if (state is DiaryStateLoading) const Loader(),
|
||||
if (state is DiaryStateLoaded) MealWidget(),
|
||||
const SizedBox(height: 12),
|
||||
CaloriesChartWidget(),
|
||||
],
|
||||
),
|
||||
|
||||
@@ -12,8 +12,8 @@ class CaloriesChartWidget extends StatelessWidget {
|
||||
builder: (BuildContext context, DiaryState state) {
|
||||
if (state is DiaryStateLoaded) {
|
||||
return Container(
|
||||
padding: const EdgeInsets.all(16),
|
||||
margin: const EdgeInsets.only(top: 12),
|
||||
padding: const EdgeInsets.all(16),
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(20),
|
||||
color: Colors.white,
|
||||
|
||||
@@ -215,6 +215,9 @@ class _FeedScreenState extends State<FeedScreen> {
|
||||
),
|
||||
SizedBox(width: 12),
|
||||
switch (_selectedChipIndex) {
|
||||
1 => _addItemButton(
|
||||
onTap: () => GoRouter.of(context).pushNamed('recipeCreateScreen'),
|
||||
),
|
||||
2 => AddItemButton(
|
||||
onTap: state.profile?.role == 'consultant' ? () => GoRouter.of(context).pushNamed(CreateDietScreen.name) : null,
|
||||
),
|
||||
|
||||
@@ -11,7 +11,7 @@ import 'package:flutter_svg/flutter_svg.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
|
||||
class MenuScreen extends StatelessWidget {
|
||||
const MenuScreen({super.key});
|
||||
MenuScreen({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
|
||||
@@ -192,10 +192,10 @@ class _ProfileScreenState extends State<ProfileScreen> {
|
||||
),
|
||||
_subscriptionsAndSubscribers(state),
|
||||
if (state.profile.badges.isNotEmpty) Padding(padding: EdgeInsets.only(bottom: 16), child: _badges(state)),
|
||||
if (state.profile.role == 'consultant') Padding(padding: EdgeInsets.only(bottom: 16), child: _socialLinks(state)),
|
||||
if (state.profile.role == 'consultant') Padding(padding: EdgeInsets.only(bottom: 16), child: _education(state)),
|
||||
if (state.profile.role == 'consultant') Padding(padding: EdgeInsets.only(bottom: 16), child: _diplomasAndCertificates(state)),
|
||||
if (state.profile.role == 'consultant') Padding(padding: EdgeInsets.only(bottom: 16), child: _activePlans(state)),
|
||||
if (state.profile.role == 'consultant') _socialLinks(state),
|
||||
if (state.profile.role == 'consultant') _education(state),
|
||||
if (state.profile.role == 'consultant') _diplomasAndCertificates(state),
|
||||
if (state.profile.role == 'consultant') _activePlans(state),
|
||||
_blogPosts(state),
|
||||
],
|
||||
),
|
||||
@@ -228,11 +228,11 @@ class _ProfileScreenState extends State<ProfileScreen> {
|
||||
),
|
||||
_subscriptionsAndSubscribers(state),
|
||||
if (state.profile.badges.isNotEmpty) Padding(padding: EdgeInsets.only(bottom: 16), child: _badges(state)),
|
||||
if (state.profile.role == 'consultant') Padding(padding: EdgeInsets.only(bottom: 16), child: _socialLinks(state)),
|
||||
if (state.profile.role == 'consultant') Padding(padding: EdgeInsets.only(bottom: 16), child: _education(state)),
|
||||
if (state.profile.role == 'consultant') Padding(padding: EdgeInsets.only(bottom: 16), child: _diplomasAndCertificates(state)),
|
||||
if (state.profile.role == 'consultant') _socialLinks(state),
|
||||
if (state.profile.role == 'consultant') _education(state),
|
||||
if (state.profile.role == 'consultant') _diplomasAndCertificates(state),
|
||||
Padding(padding: EdgeInsets.only(bottom: 16), child: _charts()),
|
||||
if (state.profile.role == 'consultant') Padding(padding: EdgeInsets.only(bottom: 16), child: _activePlans(state)),
|
||||
if (state.profile.role == 'consultant') _activePlans(state),
|
||||
_newBlogPostButton(),
|
||||
_blogPosts(state),
|
||||
],
|
||||
@@ -249,6 +249,9 @@ class _ProfileScreenState extends State<ProfileScreen> {
|
||||
bloc: plansCubit,
|
||||
builder: (BuildContext context, PlansState state) {
|
||||
if (state is PlansStateLoaded) {
|
||||
if (state.plansList.isEmpty) {
|
||||
return const SizedBox.shrink();
|
||||
}
|
||||
return Column(
|
||||
children: [
|
||||
Padding(
|
||||
@@ -366,57 +369,60 @@ class _ProfileScreenState extends State<ProfileScreen> {
|
||||
}
|
||||
|
||||
_diplomasAndCertificates(ProfileStateLoaded state) {
|
||||
if (state.profile.role != 'consultant') {
|
||||
if (state.profile.role != 'consultant' || state.profile.diplomasAndCertificates == null || state.profile.diplomasAndCertificates!.isEmpty) {
|
||||
return const SizedBox.shrink();
|
||||
}
|
||||
return Column(
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Text(
|
||||
'Дипломы\nи сертификаты',
|
||||
style: TextStyle(
|
||||
color: Color(0xFF323F4B),
|
||||
fontSize: 24,
|
||||
height: 1,
|
||||
fontWeight: FontWeight.w600,
|
||||
return Padding(
|
||||
padding: const EdgeInsets.only(bottom: 16),
|
||||
child: Column(
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Text(
|
||||
'Дипломы\nи сертификаты',
|
||||
style: TextStyle(
|
||||
color: Color(0xFF323F4B),
|
||||
fontSize: 24,
|
||||
height: 1,
|
||||
fontWeight: FontWeight.w600,
|
||||
),
|
||||
),
|
||||
),
|
||||
Text(
|
||||
widget.author == null ? 'Редактировать' : '',
|
||||
style: TextStyle(
|
||||
color: Color(0xFF2F80ED),
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.w500,
|
||||
Text(
|
||||
widget.author == null ? 'Редактировать' : '',
|
||||
style: TextStyle(
|
||||
color: Color(0xFF2F80ED),
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.w500,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
Container(
|
||||
width: MediaQuery.of(context).size.width - 2,
|
||||
padding: const EdgeInsets.symmetric(horizontal: 4),
|
||||
child: Material(
|
||||
color: Colors.white,
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
child: SingleChildScrollView(
|
||||
physics: const BouncingScrollPhysics(),
|
||||
scrollDirection: Axis.horizontal,
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12),
|
||||
child: Row(
|
||||
children: [
|
||||
if (state.profile.diplomasAndCertificates != null)
|
||||
...state.profile.diplomasAndCertificates!.map((d) => _diplomaImage(baseUrl + '/storage/' + d.path)).toList(),
|
||||
],
|
||||
const SizedBox(height: 8),
|
||||
Container(
|
||||
width: MediaQuery.of(context).size.width - 2,
|
||||
padding: const EdgeInsets.symmetric(horizontal: 4),
|
||||
child: Material(
|
||||
color: Colors.white,
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
child: SingleChildScrollView(
|
||||
physics: const BouncingScrollPhysics(),
|
||||
scrollDirection: Axis.horizontal,
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12),
|
||||
child: Row(
|
||||
children: [
|
||||
if (state.profile.diplomasAndCertificates != null)
|
||||
...state.profile.diplomasAndCertificates!.map((d) => _diplomaImage(baseUrl + '/storage/' + d.path)).toList(),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -447,53 +453,56 @@ class _ProfileScreenState extends State<ProfileScreen> {
|
||||
}
|
||||
|
||||
_education(ProfileStateLoaded state) {
|
||||
if (state.profile.role != 'consultant') {
|
||||
if (state.profile.role != 'consultant' || state.profile.educationResource?.completedCourses == null) {
|
||||
return const SizedBox.shrink();
|
||||
}
|
||||
return Column(
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Text(
|
||||
'Образование',
|
||||
style: TextStyle(
|
||||
color: Color(0xFF323F4B),
|
||||
fontSize: 24,
|
||||
fontWeight: FontWeight.w600,
|
||||
return Padding(
|
||||
padding: const EdgeInsets.only(bottom: 16),
|
||||
child: Column(
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Text(
|
||||
'Образование',
|
||||
style: TextStyle(
|
||||
color: Color(0xFF323F4B),
|
||||
fontSize: 24,
|
||||
fontWeight: FontWeight.w600,
|
||||
),
|
||||
),
|
||||
),
|
||||
Text(
|
||||
widget.author == null ? 'Редактировать' : '',
|
||||
style: TextStyle(
|
||||
color: Color(0xFF2F80ED),
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.w500,
|
||||
Text(
|
||||
widget.author == null ? 'Редактировать' : '',
|
||||
style: TextStyle(
|
||||
color: Color(0xFF2F80ED),
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.w500,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 4),
|
||||
child: Material(
|
||||
color: Colors.white,
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12),
|
||||
child: Column(
|
||||
children: [
|
||||
if (state.profile.educationResource?.completedCourses != null)
|
||||
...state.profile.educationResource!.completedCourses!.map((c) => _educationBlock(c.values.first.title, c.values.first.date)).toList(),
|
||||
],
|
||||
const SizedBox(height: 8),
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 4),
|
||||
child: Material(
|
||||
color: Colors.white,
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12),
|
||||
child: Column(
|
||||
children: [
|
||||
if (state.profile.educationResource?.completedCourses != null)
|
||||
...state.profile.educationResource!.completedCourses!.map((c) => _educationBlock(c.values.first.title, c.values.first.date)).toList(),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -539,7 +548,7 @@ class _ProfileScreenState extends State<ProfileScreen> {
|
||||
return const SizedBox.shrink();
|
||||
}
|
||||
return Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 4),
|
||||
padding: const EdgeInsets.symmetric(horizontal: 4) + const EdgeInsets.only(bottom: 16),
|
||||
child: Material(
|
||||
color: Colors.white,
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
@@ -556,17 +565,19 @@ class _ProfileScreenState extends State<ProfileScreen> {
|
||||
);
|
||||
},
|
||||
'assets/icons/vk.svg',
|
||||
state.profile.educationResource?.vkLink == null ? true : false,
|
||||
),
|
||||
const SizedBox(width: 12),
|
||||
_socialLinkButton(
|
||||
() {
|
||||
launchUrl(
|
||||
Uri.parse(
|
||||
state.profile.educationResource?.tgLink ?? '',
|
||||
state.profile.educationResource?.vkLink ?? '',
|
||||
),
|
||||
);
|
||||
},
|
||||
'assets/icons/telegram.svg',
|
||||
state.profile.educationResource?.vkLink == null ? true : false,
|
||||
),
|
||||
const SizedBox(width: 12),
|
||||
_socialLinkButton(
|
||||
@@ -578,6 +589,7 @@ class _ProfileScreenState extends State<ProfileScreen> {
|
||||
);
|
||||
},
|
||||
'assets/icons/web.svg',
|
||||
state.profile.educationResource?.personalSiteLink == null ? true : false,
|
||||
),
|
||||
],
|
||||
),
|
||||
@@ -586,9 +598,9 @@ class _ProfileScreenState extends State<ProfileScreen> {
|
||||
);
|
||||
}
|
||||
|
||||
_socialLinkButton(Function() onTap, String icon) {
|
||||
_socialLinkButton(Function() onTap, String icon, bool disabled) {
|
||||
return InkWell(
|
||||
onTap: onTap,
|
||||
onTap: disabled ? null : onTap,
|
||||
borderRadius: BorderRadius.circular(20),
|
||||
child: Ink(
|
||||
width: MediaQuery.of(context).size.width / 3 - (56 / 3),
|
||||
@@ -601,6 +613,7 @@ class _ProfileScreenState extends State<ProfileScreen> {
|
||||
child: SvgPicture.asset(
|
||||
icon,
|
||||
width: 24,
|
||||
colorFilter: ColorFilter.mode(Colors.black12, BlendMode.srcIn),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
50
lib/features/recipes/entities/recipe_create_entity.dart
Normal file
50
lib/features/recipes/entities/recipe_create_entity.dart
Normal file
@@ -0,0 +1,50 @@
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
|
||||
part 'recipe_create_entity.freezed.dart';
|
||||
|
||||
enum TypeOfRecipe {
|
||||
first_meals,
|
||||
second_meals,
|
||||
salads,
|
||||
snacks,
|
||||
sweets,
|
||||
drinks,
|
||||
other,
|
||||
}
|
||||
|
||||
@freezed
|
||||
class RecipeCreateEntity with _$RecipeCreateEntity {
|
||||
const factory RecipeCreateEntity({
|
||||
String? title,
|
||||
TypeOfRecipe? typeOfRecipe,
|
||||
int? countOfPortions,
|
||||
double? energyInOnePortion,
|
||||
String? totalCookTime, // hh:mm
|
||||
String? activeCookTime, // hh:mm
|
||||
String? description,
|
||||
File? image,
|
||||
String? videoUrl,
|
||||
required List<IngredientEntity> ingredients,
|
||||
required List<StepEntity> steps,
|
||||
required List<String> tags, // tags for search
|
||||
}) = _RecipeCreateEntity;
|
||||
}
|
||||
|
||||
@freezed
|
||||
class IngredientEntity with _$IngredientEntity {
|
||||
const factory IngredientEntity({
|
||||
required String name,
|
||||
required String unit,
|
||||
required String portion,
|
||||
}) = _IngredientEntity;
|
||||
}
|
||||
|
||||
@freezed
|
||||
class StepEntity with _$StepEntity {
|
||||
const factory StepEntity({
|
||||
String? description,
|
||||
File? image,
|
||||
}) = _StepEntity;
|
||||
}
|
||||
722
lib/features/recipes/entities/recipe_create_entity.freezed.dart
Normal file
722
lib/features/recipes/entities/recipe_create_entity.freezed.dart
Normal file
@@ -0,0 +1,722 @@
|
||||
// coverage:ignore-file
|
||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
// ignore_for_file: type=lint
|
||||
// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark
|
||||
|
||||
part of 'recipe_create_entity.dart';
|
||||
|
||||
// **************************************************************************
|
||||
// FreezedGenerator
|
||||
// **************************************************************************
|
||||
|
||||
T _$identity<T>(T value) => value;
|
||||
|
||||
final _privateConstructorUsedError = UnsupportedError(
|
||||
'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models');
|
||||
|
||||
/// @nodoc
|
||||
mixin _$RecipeCreateEntity {
|
||||
String? get title => throw _privateConstructorUsedError;
|
||||
TypeOfRecipe? get typeOfRecipe => throw _privateConstructorUsedError;
|
||||
int? get countOfPortions => throw _privateConstructorUsedError;
|
||||
double? get energyInOnePortion => throw _privateConstructorUsedError;
|
||||
String? get totalCookTime => throw _privateConstructorUsedError; // hh:mm
|
||||
String? get activeCookTime => throw _privateConstructorUsedError; // hh:mm
|
||||
String? get description => throw _privateConstructorUsedError;
|
||||
File? get image => throw _privateConstructorUsedError;
|
||||
String? get videoUrl => throw _privateConstructorUsedError;
|
||||
List<IngredientEntity> get ingredients => throw _privateConstructorUsedError;
|
||||
List<StepEntity> get steps => throw _privateConstructorUsedError;
|
||||
List<String> get tags => throw _privateConstructorUsedError;
|
||||
|
||||
/// Create a copy of RecipeCreateEntity
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
$RecipeCreateEntityCopyWith<RecipeCreateEntity> get copyWith =>
|
||||
throw _privateConstructorUsedError;
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
abstract class $RecipeCreateEntityCopyWith<$Res> {
|
||||
factory $RecipeCreateEntityCopyWith(
|
||||
RecipeCreateEntity value, $Res Function(RecipeCreateEntity) then) =
|
||||
_$RecipeCreateEntityCopyWithImpl<$Res, RecipeCreateEntity>;
|
||||
@useResult
|
||||
$Res call(
|
||||
{String? title,
|
||||
TypeOfRecipe? typeOfRecipe,
|
||||
int? countOfPortions,
|
||||
double? energyInOnePortion,
|
||||
String? totalCookTime,
|
||||
String? activeCookTime,
|
||||
String? description,
|
||||
File? image,
|
||||
String? videoUrl,
|
||||
List<IngredientEntity> ingredients,
|
||||
List<StepEntity> steps,
|
||||
List<String> tags});
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
class _$RecipeCreateEntityCopyWithImpl<$Res, $Val extends RecipeCreateEntity>
|
||||
implements $RecipeCreateEntityCopyWith<$Res> {
|
||||
_$RecipeCreateEntityCopyWithImpl(this._value, this._then);
|
||||
|
||||
// ignore: unused_field
|
||||
final $Val _value;
|
||||
// ignore: unused_field
|
||||
final $Res Function($Val) _then;
|
||||
|
||||
/// Create a copy of RecipeCreateEntity
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@pragma('vm:prefer-inline')
|
||||
@override
|
||||
$Res call({
|
||||
Object? title = freezed,
|
||||
Object? typeOfRecipe = freezed,
|
||||
Object? countOfPortions = freezed,
|
||||
Object? energyInOnePortion = freezed,
|
||||
Object? totalCookTime = freezed,
|
||||
Object? activeCookTime = freezed,
|
||||
Object? description = freezed,
|
||||
Object? image = freezed,
|
||||
Object? videoUrl = freezed,
|
||||
Object? ingredients = null,
|
||||
Object? steps = null,
|
||||
Object? tags = null,
|
||||
}) {
|
||||
return _then(_value.copyWith(
|
||||
title: freezed == title
|
||||
? _value.title
|
||||
: title // ignore: cast_nullable_to_non_nullable
|
||||
as String?,
|
||||
typeOfRecipe: freezed == typeOfRecipe
|
||||
? _value.typeOfRecipe
|
||||
: typeOfRecipe // ignore: cast_nullable_to_non_nullable
|
||||
as TypeOfRecipe?,
|
||||
countOfPortions: freezed == countOfPortions
|
||||
? _value.countOfPortions
|
||||
: countOfPortions // ignore: cast_nullable_to_non_nullable
|
||||
as int?,
|
||||
energyInOnePortion: freezed == energyInOnePortion
|
||||
? _value.energyInOnePortion
|
||||
: energyInOnePortion // ignore: cast_nullable_to_non_nullable
|
||||
as double?,
|
||||
totalCookTime: freezed == totalCookTime
|
||||
? _value.totalCookTime
|
||||
: totalCookTime // ignore: cast_nullable_to_non_nullable
|
||||
as String?,
|
||||
activeCookTime: freezed == activeCookTime
|
||||
? _value.activeCookTime
|
||||
: activeCookTime // ignore: cast_nullable_to_non_nullable
|
||||
as String?,
|
||||
description: freezed == description
|
||||
? _value.description
|
||||
: description // ignore: cast_nullable_to_non_nullable
|
||||
as String?,
|
||||
image: freezed == image
|
||||
? _value.image
|
||||
: image // ignore: cast_nullable_to_non_nullable
|
||||
as File?,
|
||||
videoUrl: freezed == videoUrl
|
||||
? _value.videoUrl
|
||||
: videoUrl // ignore: cast_nullable_to_non_nullable
|
||||
as String?,
|
||||
ingredients: null == ingredients
|
||||
? _value.ingredients
|
||||
: ingredients // ignore: cast_nullable_to_non_nullable
|
||||
as List<IngredientEntity>,
|
||||
steps: null == steps
|
||||
? _value.steps
|
||||
: steps // ignore: cast_nullable_to_non_nullable
|
||||
as List<StepEntity>,
|
||||
tags: null == tags
|
||||
? _value.tags
|
||||
: tags // ignore: cast_nullable_to_non_nullable
|
||||
as List<String>,
|
||||
) as $Val);
|
||||
}
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
abstract class _$$RecipeCreateEntityImplCopyWith<$Res>
|
||||
implements $RecipeCreateEntityCopyWith<$Res> {
|
||||
factory _$$RecipeCreateEntityImplCopyWith(_$RecipeCreateEntityImpl value,
|
||||
$Res Function(_$RecipeCreateEntityImpl) then) =
|
||||
__$$RecipeCreateEntityImplCopyWithImpl<$Res>;
|
||||
@override
|
||||
@useResult
|
||||
$Res call(
|
||||
{String? title,
|
||||
TypeOfRecipe? typeOfRecipe,
|
||||
int? countOfPortions,
|
||||
double? energyInOnePortion,
|
||||
String? totalCookTime,
|
||||
String? activeCookTime,
|
||||
String? description,
|
||||
File? image,
|
||||
String? videoUrl,
|
||||
List<IngredientEntity> ingredients,
|
||||
List<StepEntity> steps,
|
||||
List<String> tags});
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
class __$$RecipeCreateEntityImplCopyWithImpl<$Res>
|
||||
extends _$RecipeCreateEntityCopyWithImpl<$Res, _$RecipeCreateEntityImpl>
|
||||
implements _$$RecipeCreateEntityImplCopyWith<$Res> {
|
||||
__$$RecipeCreateEntityImplCopyWithImpl(_$RecipeCreateEntityImpl _value,
|
||||
$Res Function(_$RecipeCreateEntityImpl) _then)
|
||||
: super(_value, _then);
|
||||
|
||||
/// Create a copy of RecipeCreateEntity
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@pragma('vm:prefer-inline')
|
||||
@override
|
||||
$Res call({
|
||||
Object? title = freezed,
|
||||
Object? typeOfRecipe = freezed,
|
||||
Object? countOfPortions = freezed,
|
||||
Object? energyInOnePortion = freezed,
|
||||
Object? totalCookTime = freezed,
|
||||
Object? activeCookTime = freezed,
|
||||
Object? description = freezed,
|
||||
Object? image = freezed,
|
||||
Object? videoUrl = freezed,
|
||||
Object? ingredients = null,
|
||||
Object? steps = null,
|
||||
Object? tags = null,
|
||||
}) {
|
||||
return _then(_$RecipeCreateEntityImpl(
|
||||
title: freezed == title
|
||||
? _value.title
|
||||
: title // ignore: cast_nullable_to_non_nullable
|
||||
as String?,
|
||||
typeOfRecipe: freezed == typeOfRecipe
|
||||
? _value.typeOfRecipe
|
||||
: typeOfRecipe // ignore: cast_nullable_to_non_nullable
|
||||
as TypeOfRecipe?,
|
||||
countOfPortions: freezed == countOfPortions
|
||||
? _value.countOfPortions
|
||||
: countOfPortions // ignore: cast_nullable_to_non_nullable
|
||||
as int?,
|
||||
energyInOnePortion: freezed == energyInOnePortion
|
||||
? _value.energyInOnePortion
|
||||
: energyInOnePortion // ignore: cast_nullable_to_non_nullable
|
||||
as double?,
|
||||
totalCookTime: freezed == totalCookTime
|
||||
? _value.totalCookTime
|
||||
: totalCookTime // ignore: cast_nullable_to_non_nullable
|
||||
as String?,
|
||||
activeCookTime: freezed == activeCookTime
|
||||
? _value.activeCookTime
|
||||
: activeCookTime // ignore: cast_nullable_to_non_nullable
|
||||
as String?,
|
||||
description: freezed == description
|
||||
? _value.description
|
||||
: description // ignore: cast_nullable_to_non_nullable
|
||||
as String?,
|
||||
image: freezed == image
|
||||
? _value.image
|
||||
: image // ignore: cast_nullable_to_non_nullable
|
||||
as File?,
|
||||
videoUrl: freezed == videoUrl
|
||||
? _value.videoUrl
|
||||
: videoUrl // ignore: cast_nullable_to_non_nullable
|
||||
as String?,
|
||||
ingredients: null == ingredients
|
||||
? _value._ingredients
|
||||
: ingredients // ignore: cast_nullable_to_non_nullable
|
||||
as List<IngredientEntity>,
|
||||
steps: null == steps
|
||||
? _value._steps
|
||||
: steps // ignore: cast_nullable_to_non_nullable
|
||||
as List<StepEntity>,
|
||||
tags: null == tags
|
||||
? _value._tags
|
||||
: tags // ignore: cast_nullable_to_non_nullable
|
||||
as List<String>,
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
|
||||
class _$RecipeCreateEntityImpl implements _RecipeCreateEntity {
|
||||
const _$RecipeCreateEntityImpl(
|
||||
{this.title,
|
||||
this.typeOfRecipe,
|
||||
this.countOfPortions,
|
||||
this.energyInOnePortion,
|
||||
this.totalCookTime,
|
||||
this.activeCookTime,
|
||||
this.description,
|
||||
this.image,
|
||||
this.videoUrl,
|
||||
required final List<IngredientEntity> ingredients,
|
||||
required final List<StepEntity> steps,
|
||||
required final List<String> tags})
|
||||
: _ingredients = ingredients,
|
||||
_steps = steps,
|
||||
_tags = tags;
|
||||
|
||||
@override
|
||||
final String? title;
|
||||
@override
|
||||
final TypeOfRecipe? typeOfRecipe;
|
||||
@override
|
||||
final int? countOfPortions;
|
||||
@override
|
||||
final double? energyInOnePortion;
|
||||
@override
|
||||
final String? totalCookTime;
|
||||
// hh:mm
|
||||
@override
|
||||
final String? activeCookTime;
|
||||
// hh:mm
|
||||
@override
|
||||
final String? description;
|
||||
@override
|
||||
final File? image;
|
||||
@override
|
||||
final String? videoUrl;
|
||||
final List<IngredientEntity> _ingredients;
|
||||
@override
|
||||
List<IngredientEntity> get ingredients {
|
||||
if (_ingredients is EqualUnmodifiableListView) return _ingredients;
|
||||
// ignore: implicit_dynamic_type
|
||||
return EqualUnmodifiableListView(_ingredients);
|
||||
}
|
||||
|
||||
final List<StepEntity> _steps;
|
||||
@override
|
||||
List<StepEntity> get steps {
|
||||
if (_steps is EqualUnmodifiableListView) return _steps;
|
||||
// ignore: implicit_dynamic_type
|
||||
return EqualUnmodifiableListView(_steps);
|
||||
}
|
||||
|
||||
final List<String> _tags;
|
||||
@override
|
||||
List<String> get tags {
|
||||
if (_tags is EqualUnmodifiableListView) return _tags;
|
||||
// ignore: implicit_dynamic_type
|
||||
return EqualUnmodifiableListView(_tags);
|
||||
}
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'RecipeCreateEntity(title: $title, typeOfRecipe: $typeOfRecipe, countOfPortions: $countOfPortions, energyInOnePortion: $energyInOnePortion, totalCookTime: $totalCookTime, activeCookTime: $activeCookTime, description: $description, image: $image, videoUrl: $videoUrl, ingredients: $ingredients, steps: $steps, tags: $tags)';
|
||||
}
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
return identical(this, other) ||
|
||||
(other.runtimeType == runtimeType &&
|
||||
other is _$RecipeCreateEntityImpl &&
|
||||
(identical(other.title, title) || other.title == title) &&
|
||||
(identical(other.typeOfRecipe, typeOfRecipe) ||
|
||||
other.typeOfRecipe == typeOfRecipe) &&
|
||||
(identical(other.countOfPortions, countOfPortions) ||
|
||||
other.countOfPortions == countOfPortions) &&
|
||||
(identical(other.energyInOnePortion, energyInOnePortion) ||
|
||||
other.energyInOnePortion == energyInOnePortion) &&
|
||||
(identical(other.totalCookTime, totalCookTime) ||
|
||||
other.totalCookTime == totalCookTime) &&
|
||||
(identical(other.activeCookTime, activeCookTime) ||
|
||||
other.activeCookTime == activeCookTime) &&
|
||||
(identical(other.description, description) ||
|
||||
other.description == description) &&
|
||||
(identical(other.image, image) || other.image == image) &&
|
||||
(identical(other.videoUrl, videoUrl) ||
|
||||
other.videoUrl == videoUrl) &&
|
||||
const DeepCollectionEquality()
|
||||
.equals(other._ingredients, _ingredients) &&
|
||||
const DeepCollectionEquality().equals(other._steps, _steps) &&
|
||||
const DeepCollectionEquality().equals(other._tags, _tags));
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode => Object.hash(
|
||||
runtimeType,
|
||||
title,
|
||||
typeOfRecipe,
|
||||
countOfPortions,
|
||||
energyInOnePortion,
|
||||
totalCookTime,
|
||||
activeCookTime,
|
||||
description,
|
||||
image,
|
||||
videoUrl,
|
||||
const DeepCollectionEquality().hash(_ingredients),
|
||||
const DeepCollectionEquality().hash(_steps),
|
||||
const DeepCollectionEquality().hash(_tags));
|
||||
|
||||
/// Create a copy of RecipeCreateEntity
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
@override
|
||||
@pragma('vm:prefer-inline')
|
||||
_$$RecipeCreateEntityImplCopyWith<_$RecipeCreateEntityImpl> get copyWith =>
|
||||
__$$RecipeCreateEntityImplCopyWithImpl<_$RecipeCreateEntityImpl>(
|
||||
this, _$identity);
|
||||
}
|
||||
|
||||
abstract class _RecipeCreateEntity implements RecipeCreateEntity {
|
||||
const factory _RecipeCreateEntity(
|
||||
{final String? title,
|
||||
final TypeOfRecipe? typeOfRecipe,
|
||||
final int? countOfPortions,
|
||||
final double? energyInOnePortion,
|
||||
final String? totalCookTime,
|
||||
final String? activeCookTime,
|
||||
final String? description,
|
||||
final File? image,
|
||||
final String? videoUrl,
|
||||
required final List<IngredientEntity> ingredients,
|
||||
required final List<StepEntity> steps,
|
||||
required final List<String> tags}) = _$RecipeCreateEntityImpl;
|
||||
|
||||
@override
|
||||
String? get title;
|
||||
@override
|
||||
TypeOfRecipe? get typeOfRecipe;
|
||||
@override
|
||||
int? get countOfPortions;
|
||||
@override
|
||||
double? get energyInOnePortion;
|
||||
@override
|
||||
String? get totalCookTime; // hh:mm
|
||||
@override
|
||||
String? get activeCookTime; // hh:mm
|
||||
@override
|
||||
String? get description;
|
||||
@override
|
||||
File? get image;
|
||||
@override
|
||||
String? get videoUrl;
|
||||
@override
|
||||
List<IngredientEntity> get ingredients;
|
||||
@override
|
||||
List<StepEntity> get steps;
|
||||
@override
|
||||
List<String> get tags;
|
||||
|
||||
/// Create a copy of RecipeCreateEntity
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@override
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
_$$RecipeCreateEntityImplCopyWith<_$RecipeCreateEntityImpl> get copyWith =>
|
||||
throw _privateConstructorUsedError;
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
mixin _$IngredientEntity {
|
||||
String get name => throw _privateConstructorUsedError;
|
||||
String get unit => throw _privateConstructorUsedError;
|
||||
String get portion => throw _privateConstructorUsedError;
|
||||
|
||||
/// Create a copy of IngredientEntity
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
$IngredientEntityCopyWith<IngredientEntity> get copyWith =>
|
||||
throw _privateConstructorUsedError;
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
abstract class $IngredientEntityCopyWith<$Res> {
|
||||
factory $IngredientEntityCopyWith(
|
||||
IngredientEntity value, $Res Function(IngredientEntity) then) =
|
||||
_$IngredientEntityCopyWithImpl<$Res, IngredientEntity>;
|
||||
@useResult
|
||||
$Res call({String name, String unit, String portion});
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
class _$IngredientEntityCopyWithImpl<$Res, $Val extends IngredientEntity>
|
||||
implements $IngredientEntityCopyWith<$Res> {
|
||||
_$IngredientEntityCopyWithImpl(this._value, this._then);
|
||||
|
||||
// ignore: unused_field
|
||||
final $Val _value;
|
||||
// ignore: unused_field
|
||||
final $Res Function($Val) _then;
|
||||
|
||||
/// Create a copy of IngredientEntity
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@pragma('vm:prefer-inline')
|
||||
@override
|
||||
$Res call({
|
||||
Object? name = null,
|
||||
Object? unit = null,
|
||||
Object? portion = null,
|
||||
}) {
|
||||
return _then(_value.copyWith(
|
||||
name: null == name
|
||||
? _value.name
|
||||
: name // ignore: cast_nullable_to_non_nullable
|
||||
as String,
|
||||
unit: null == unit
|
||||
? _value.unit
|
||||
: unit // ignore: cast_nullable_to_non_nullable
|
||||
as String,
|
||||
portion: null == portion
|
||||
? _value.portion
|
||||
: portion // ignore: cast_nullable_to_non_nullable
|
||||
as String,
|
||||
) as $Val);
|
||||
}
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
abstract class _$$IngredientEntityImplCopyWith<$Res>
|
||||
implements $IngredientEntityCopyWith<$Res> {
|
||||
factory _$$IngredientEntityImplCopyWith(_$IngredientEntityImpl value,
|
||||
$Res Function(_$IngredientEntityImpl) then) =
|
||||
__$$IngredientEntityImplCopyWithImpl<$Res>;
|
||||
@override
|
||||
@useResult
|
||||
$Res call({String name, String unit, String portion});
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
class __$$IngredientEntityImplCopyWithImpl<$Res>
|
||||
extends _$IngredientEntityCopyWithImpl<$Res, _$IngredientEntityImpl>
|
||||
implements _$$IngredientEntityImplCopyWith<$Res> {
|
||||
__$$IngredientEntityImplCopyWithImpl(_$IngredientEntityImpl _value,
|
||||
$Res Function(_$IngredientEntityImpl) _then)
|
||||
: super(_value, _then);
|
||||
|
||||
/// Create a copy of IngredientEntity
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@pragma('vm:prefer-inline')
|
||||
@override
|
||||
$Res call({
|
||||
Object? name = null,
|
||||
Object? unit = null,
|
||||
Object? portion = null,
|
||||
}) {
|
||||
return _then(_$IngredientEntityImpl(
|
||||
name: null == name
|
||||
? _value.name
|
||||
: name // ignore: cast_nullable_to_non_nullable
|
||||
as String,
|
||||
unit: null == unit
|
||||
? _value.unit
|
||||
: unit // ignore: cast_nullable_to_non_nullable
|
||||
as String,
|
||||
portion: null == portion
|
||||
? _value.portion
|
||||
: portion // ignore: cast_nullable_to_non_nullable
|
||||
as String,
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
|
||||
class _$IngredientEntityImpl implements _IngredientEntity {
|
||||
const _$IngredientEntityImpl(
|
||||
{required this.name, required this.unit, required this.portion});
|
||||
|
||||
@override
|
||||
final String name;
|
||||
@override
|
||||
final String unit;
|
||||
@override
|
||||
final String portion;
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'IngredientEntity(name: $name, unit: $unit, portion: $portion)';
|
||||
}
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
return identical(this, other) ||
|
||||
(other.runtimeType == runtimeType &&
|
||||
other is _$IngredientEntityImpl &&
|
||||
(identical(other.name, name) || other.name == name) &&
|
||||
(identical(other.unit, unit) || other.unit == unit) &&
|
||||
(identical(other.portion, portion) || other.portion == portion));
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode => Object.hash(runtimeType, name, unit, portion);
|
||||
|
||||
/// Create a copy of IngredientEntity
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
@override
|
||||
@pragma('vm:prefer-inline')
|
||||
_$$IngredientEntityImplCopyWith<_$IngredientEntityImpl> get copyWith =>
|
||||
__$$IngredientEntityImplCopyWithImpl<_$IngredientEntityImpl>(
|
||||
this, _$identity);
|
||||
}
|
||||
|
||||
abstract class _IngredientEntity implements IngredientEntity {
|
||||
const factory _IngredientEntity(
|
||||
{required final String name,
|
||||
required final String unit,
|
||||
required final String portion}) = _$IngredientEntityImpl;
|
||||
|
||||
@override
|
||||
String get name;
|
||||
@override
|
||||
String get unit;
|
||||
@override
|
||||
String get portion;
|
||||
|
||||
/// Create a copy of IngredientEntity
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@override
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
_$$IngredientEntityImplCopyWith<_$IngredientEntityImpl> get copyWith =>
|
||||
throw _privateConstructorUsedError;
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
mixin _$StepEntity {
|
||||
String? get description => throw _privateConstructorUsedError;
|
||||
File? get image => throw _privateConstructorUsedError;
|
||||
|
||||
/// Create a copy of StepEntity
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
$StepEntityCopyWith<StepEntity> get copyWith =>
|
||||
throw _privateConstructorUsedError;
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
abstract class $StepEntityCopyWith<$Res> {
|
||||
factory $StepEntityCopyWith(
|
||||
StepEntity value, $Res Function(StepEntity) then) =
|
||||
_$StepEntityCopyWithImpl<$Res, StepEntity>;
|
||||
@useResult
|
||||
$Res call({String? description, File? image});
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
class _$StepEntityCopyWithImpl<$Res, $Val extends StepEntity>
|
||||
implements $StepEntityCopyWith<$Res> {
|
||||
_$StepEntityCopyWithImpl(this._value, this._then);
|
||||
|
||||
// ignore: unused_field
|
||||
final $Val _value;
|
||||
// ignore: unused_field
|
||||
final $Res Function($Val) _then;
|
||||
|
||||
/// Create a copy of StepEntity
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@pragma('vm:prefer-inline')
|
||||
@override
|
||||
$Res call({
|
||||
Object? description = freezed,
|
||||
Object? image = freezed,
|
||||
}) {
|
||||
return _then(_value.copyWith(
|
||||
description: freezed == description
|
||||
? _value.description
|
||||
: description // ignore: cast_nullable_to_non_nullable
|
||||
as String?,
|
||||
image: freezed == image
|
||||
? _value.image
|
||||
: image // ignore: cast_nullable_to_non_nullable
|
||||
as File?,
|
||||
) as $Val);
|
||||
}
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
abstract class _$$StepEntityImplCopyWith<$Res>
|
||||
implements $StepEntityCopyWith<$Res> {
|
||||
factory _$$StepEntityImplCopyWith(
|
||||
_$StepEntityImpl value, $Res Function(_$StepEntityImpl) then) =
|
||||
__$$StepEntityImplCopyWithImpl<$Res>;
|
||||
@override
|
||||
@useResult
|
||||
$Res call({String? description, File? image});
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
class __$$StepEntityImplCopyWithImpl<$Res>
|
||||
extends _$StepEntityCopyWithImpl<$Res, _$StepEntityImpl>
|
||||
implements _$$StepEntityImplCopyWith<$Res> {
|
||||
__$$StepEntityImplCopyWithImpl(
|
||||
_$StepEntityImpl _value, $Res Function(_$StepEntityImpl) _then)
|
||||
: super(_value, _then);
|
||||
|
||||
/// Create a copy of StepEntity
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@pragma('vm:prefer-inline')
|
||||
@override
|
||||
$Res call({
|
||||
Object? description = freezed,
|
||||
Object? image = freezed,
|
||||
}) {
|
||||
return _then(_$StepEntityImpl(
|
||||
description: freezed == description
|
||||
? _value.description
|
||||
: description // ignore: cast_nullable_to_non_nullable
|
||||
as String?,
|
||||
image: freezed == image
|
||||
? _value.image
|
||||
: image // ignore: cast_nullable_to_non_nullable
|
||||
as File?,
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
|
||||
class _$StepEntityImpl implements _StepEntity {
|
||||
const _$StepEntityImpl({this.description, this.image});
|
||||
|
||||
@override
|
||||
final String? description;
|
||||
@override
|
||||
final File? image;
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'StepEntity(description: $description, image: $image)';
|
||||
}
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
return identical(this, other) ||
|
||||
(other.runtimeType == runtimeType &&
|
||||
other is _$StepEntityImpl &&
|
||||
(identical(other.description, description) ||
|
||||
other.description == description) &&
|
||||
(identical(other.image, image) || other.image == image));
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode => Object.hash(runtimeType, description, image);
|
||||
|
||||
/// Create a copy of StepEntity
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
@override
|
||||
@pragma('vm:prefer-inline')
|
||||
_$$StepEntityImplCopyWith<_$StepEntityImpl> get copyWith =>
|
||||
__$$StepEntityImplCopyWithImpl<_$StepEntityImpl>(this, _$identity);
|
||||
}
|
||||
|
||||
abstract class _StepEntity implements StepEntity {
|
||||
const factory _StepEntity({final String? description, final File? image}) =
|
||||
_$StepEntityImpl;
|
||||
|
||||
@override
|
||||
String? get description;
|
||||
@override
|
||||
File? get image;
|
||||
|
||||
/// Create a copy of StepEntity
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@override
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
_$$StepEntityImplCopyWith<_$StepEntityImpl> get copyWith =>
|
||||
throw _privateConstructorUsedError;
|
||||
}
|
||||
@@ -0,0 +1,219 @@
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:eatvio_app/features/recipes/entities/recipe_create_entity.dart';
|
||||
import 'package:eatvio_app/features/recipes/presentation/bloc/create_recipe_cubit/create_recipe_state.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
|
||||
class CreateRecipeCubit extends Cubit<CreateRecipeState> {
|
||||
CreateRecipeCubit()
|
||||
: super(
|
||||
CreateRecipeState(
|
||||
RecipeCreateEntity(
|
||||
ingredients: [],
|
||||
steps: [],
|
||||
tags: [],
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
addStep() {
|
||||
emit(
|
||||
CreateRecipeState(
|
||||
state.entity.copyWith(
|
||||
steps: [
|
||||
...state.entity.steps,
|
||||
StepEntity(),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
removeStep(StepEntity step) {
|
||||
List<StepEntity> steps = [...state.entity.steps];
|
||||
steps.remove(step);
|
||||
|
||||
emit(
|
||||
CreateRecipeState(state.entity.copyWith(steps: steps)),
|
||||
);
|
||||
}
|
||||
|
||||
addIngredient(IngredientEntity entity) {
|
||||
emit(
|
||||
CreateRecipeState(
|
||||
state.entity.copyWith(
|
||||
ingredients: [
|
||||
...state.entity.ingredients,
|
||||
entity,
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
removeIngredient(int index) {
|
||||
List<IngredientEntity> ingredients = [...state.entity.ingredients];
|
||||
ingredients.removeAt(index);
|
||||
|
||||
emit(
|
||||
CreateRecipeState(
|
||||
state.entity.copyWith(
|
||||
ingredients: ingredients,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
replaceIngredientPosition(int oldIndex, int newIndex) {
|
||||
List<IngredientEntity> ingredients = [...state.entity.ingredients];
|
||||
final IngredientEntity item = ingredients.removeAt(oldIndex);
|
||||
ingredients.insert(newIndex, item);
|
||||
emit(
|
||||
CreateRecipeState(
|
||||
state.entity.copyWith(
|
||||
ingredients: ingredients,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
replaceTitle(String title) {
|
||||
emit(
|
||||
CreateRecipeState(
|
||||
state.entity.copyWith(
|
||||
title: title,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
replaceDescription(String description) {
|
||||
emit(
|
||||
CreateRecipeState(
|
||||
state.entity.copyWith(
|
||||
description: description,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
changeType(TypeOfRecipe typeOfRecipe) {
|
||||
emit(
|
||||
CreateRecipeState(
|
||||
state.entity.copyWith(
|
||||
typeOfRecipe: typeOfRecipe,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
addTag(String tag) {
|
||||
emit(
|
||||
CreateRecipeState(
|
||||
state.entity.copyWith(
|
||||
tags: [
|
||||
...state.entity.tags,
|
||||
tag,
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
removeTag(String tag) {
|
||||
List<String> tags = [...state.entity.tags];
|
||||
tags.remove(tag);
|
||||
|
||||
emit(
|
||||
CreateRecipeState(
|
||||
state.entity.copyWith(
|
||||
tags: tags,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
replacePortionsCount(int count) {
|
||||
emit(
|
||||
CreateRecipeState(
|
||||
state.entity.copyWith(
|
||||
countOfPortions: count,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
replaceEnegryInPortion(double energy) {
|
||||
emit(
|
||||
CreateRecipeState(
|
||||
state.entity.copyWith(
|
||||
energyInOnePortion: energy,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
replaceTotalTime(String time) {
|
||||
emit(
|
||||
CreateRecipeState(
|
||||
state.entity.copyWith(
|
||||
totalCookTime: time,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
replaceActiveTime(String time) {
|
||||
emit(
|
||||
CreateRecipeState(
|
||||
state.entity.copyWith(
|
||||
activeCookTime: time,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
replaceVideoUrl(String videoUrl) {
|
||||
emit(
|
||||
CreateRecipeState(
|
||||
state.entity.copyWith(
|
||||
videoUrl: videoUrl,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
replaceImage(File? image) {
|
||||
emit(
|
||||
CreateRecipeState(
|
||||
state.entity.copyWith(
|
||||
image: image,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
replaceDescriptionInStep(String description, int index) {
|
||||
List<StepEntity> steps = [...state.entity.steps];
|
||||
steps[index] = steps[index].copyWith(description: description);
|
||||
emit(
|
||||
CreateRecipeState(
|
||||
state.entity.copyWith(
|
||||
steps: steps,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
replaceImageInStep(File? image, int index) {
|
||||
List<StepEntity> steps = [...state.entity.steps];
|
||||
steps[index] = steps[index].copyWith(image: image);
|
||||
emit(
|
||||
CreateRecipeState(
|
||||
state.entity.copyWith(
|
||||
steps: steps,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
import 'package:eatvio_app/features/recipes/entities/recipe_create_entity.dart';
|
||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
|
||||
part 'create_recipe_state.freezed.dart';
|
||||
|
||||
@freezed
|
||||
class CreateRecipeState with _$CreateRecipeState {
|
||||
const factory CreateRecipeState(
|
||||
RecipeCreateEntity entity,
|
||||
) = _CreateRecipeState;
|
||||
}
|
||||
@@ -0,0 +1,160 @@
|
||||
// coverage:ignore-file
|
||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
// ignore_for_file: type=lint
|
||||
// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark
|
||||
|
||||
part of 'create_recipe_state.dart';
|
||||
|
||||
// **************************************************************************
|
||||
// FreezedGenerator
|
||||
// **************************************************************************
|
||||
|
||||
T _$identity<T>(T value) => value;
|
||||
|
||||
final _privateConstructorUsedError = UnsupportedError(
|
||||
'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models');
|
||||
|
||||
/// @nodoc
|
||||
mixin _$CreateRecipeState {
|
||||
RecipeCreateEntity get entity => throw _privateConstructorUsedError;
|
||||
|
||||
/// Create a copy of CreateRecipeState
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
$CreateRecipeStateCopyWith<CreateRecipeState> get copyWith =>
|
||||
throw _privateConstructorUsedError;
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
abstract class $CreateRecipeStateCopyWith<$Res> {
|
||||
factory $CreateRecipeStateCopyWith(
|
||||
CreateRecipeState value, $Res Function(CreateRecipeState) then) =
|
||||
_$CreateRecipeStateCopyWithImpl<$Res, CreateRecipeState>;
|
||||
@useResult
|
||||
$Res call({RecipeCreateEntity entity});
|
||||
|
||||
$RecipeCreateEntityCopyWith<$Res> get entity;
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
class _$CreateRecipeStateCopyWithImpl<$Res, $Val extends CreateRecipeState>
|
||||
implements $CreateRecipeStateCopyWith<$Res> {
|
||||
_$CreateRecipeStateCopyWithImpl(this._value, this._then);
|
||||
|
||||
// ignore: unused_field
|
||||
final $Val _value;
|
||||
// ignore: unused_field
|
||||
final $Res Function($Val) _then;
|
||||
|
||||
/// Create a copy of CreateRecipeState
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@pragma('vm:prefer-inline')
|
||||
@override
|
||||
$Res call({
|
||||
Object? entity = null,
|
||||
}) {
|
||||
return _then(_value.copyWith(
|
||||
entity: null == entity
|
||||
? _value.entity
|
||||
: entity // ignore: cast_nullable_to_non_nullable
|
||||
as RecipeCreateEntity,
|
||||
) as $Val);
|
||||
}
|
||||
|
||||
/// Create a copy of CreateRecipeState
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@override
|
||||
@pragma('vm:prefer-inline')
|
||||
$RecipeCreateEntityCopyWith<$Res> get entity {
|
||||
return $RecipeCreateEntityCopyWith<$Res>(_value.entity, (value) {
|
||||
return _then(_value.copyWith(entity: value) as $Val);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
abstract class _$$CreateRecipeStateImplCopyWith<$Res>
|
||||
implements $CreateRecipeStateCopyWith<$Res> {
|
||||
factory _$$CreateRecipeStateImplCopyWith(_$CreateRecipeStateImpl value,
|
||||
$Res Function(_$CreateRecipeStateImpl) then) =
|
||||
__$$CreateRecipeStateImplCopyWithImpl<$Res>;
|
||||
@override
|
||||
@useResult
|
||||
$Res call({RecipeCreateEntity entity});
|
||||
|
||||
@override
|
||||
$RecipeCreateEntityCopyWith<$Res> get entity;
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
class __$$CreateRecipeStateImplCopyWithImpl<$Res>
|
||||
extends _$CreateRecipeStateCopyWithImpl<$Res, _$CreateRecipeStateImpl>
|
||||
implements _$$CreateRecipeStateImplCopyWith<$Res> {
|
||||
__$$CreateRecipeStateImplCopyWithImpl(_$CreateRecipeStateImpl _value,
|
||||
$Res Function(_$CreateRecipeStateImpl) _then)
|
||||
: super(_value, _then);
|
||||
|
||||
/// Create a copy of CreateRecipeState
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@pragma('vm:prefer-inline')
|
||||
@override
|
||||
$Res call({
|
||||
Object? entity = null,
|
||||
}) {
|
||||
return _then(_$CreateRecipeStateImpl(
|
||||
null == entity
|
||||
? _value.entity
|
||||
: entity // ignore: cast_nullable_to_non_nullable
|
||||
as RecipeCreateEntity,
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
|
||||
class _$CreateRecipeStateImpl implements _CreateRecipeState {
|
||||
const _$CreateRecipeStateImpl(this.entity);
|
||||
|
||||
@override
|
||||
final RecipeCreateEntity entity;
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'CreateRecipeState(entity: $entity)';
|
||||
}
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
return identical(this, other) ||
|
||||
(other.runtimeType == runtimeType &&
|
||||
other is _$CreateRecipeStateImpl &&
|
||||
(identical(other.entity, entity) || other.entity == entity));
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode => Object.hash(runtimeType, entity);
|
||||
|
||||
/// Create a copy of CreateRecipeState
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
@override
|
||||
@pragma('vm:prefer-inline')
|
||||
_$$CreateRecipeStateImplCopyWith<_$CreateRecipeStateImpl> get copyWith =>
|
||||
__$$CreateRecipeStateImplCopyWithImpl<_$CreateRecipeStateImpl>(
|
||||
this, _$identity);
|
||||
}
|
||||
|
||||
abstract class _CreateRecipeState implements CreateRecipeState {
|
||||
const factory _CreateRecipeState(final RecipeCreateEntity entity) =
|
||||
_$CreateRecipeStateImpl;
|
||||
|
||||
@override
|
||||
RecipeCreateEntity get entity;
|
||||
|
||||
/// Create a copy of CreateRecipeState
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@override
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
_$$CreateRecipeStateImplCopyWith<_$CreateRecipeStateImpl> get copyWith =>
|
||||
throw _privateConstructorUsedError;
|
||||
}
|
||||
@@ -1,300 +0,0 @@
|
||||
import 'package:eatvio_app/core/data/selectionPopupModel/selection_popup_model.dart';
|
||||
import 'package:eatvio_app/features/recipes/models/ingredient.dart';
|
||||
import 'package:eatvio_app/features/recipes/models/recipe_model.dart';
|
||||
import 'package:eatvio_app/features/recipes/presentation/bloc/recipe_create_state.dart';
|
||||
import 'package:eatvio_app/features/recipes/reporitories/recipe_repository.dart';
|
||||
import 'package:eatvio_app/features/recipes/models/product.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:image_picker/image_picker.dart';
|
||||
|
||||
class RecipeCreateCubit extends Cubit<RecipeCreateState> {
|
||||
final RecipeRepository recipeRepository;
|
||||
|
||||
RecipeCreateCubit({required this.recipeRepository}) : super(const RecipeCreateState.loading());
|
||||
|
||||
loadRecipeById(String recipeID) async {
|
||||
emit(const RecipeCreateStateLoading());
|
||||
try {
|
||||
final RecipeModel recipe = await recipeRepository.getRecipe(recipeID);
|
||||
final List<SelectionPopupModel> types = [
|
||||
SelectionPopupModel(title: 'Первые блюда', value: 'first_meals'),
|
||||
SelectionPopupModel(title: 'Вторые блюда', value: 'second_meals'),
|
||||
SelectionPopupModel(title: 'Cалаты', value: 'salads'),
|
||||
SelectionPopupModel(title: 'Перекусы/закуски', value: 'snacks'),
|
||||
SelectionPopupModel(title: 'Сладости', value: 'sweets'),
|
||||
SelectionPopupModel(title: 'Напитки', value: 'drinks'),
|
||||
SelectionPopupModel(title: 'Прочее', value: 'other'),
|
||||
];
|
||||
final List<SelectionPopupModel> cookingTimes = [
|
||||
SelectionPopupModel(title: '10 минут', value: 10),
|
||||
SelectionPopupModel(title: '20 минут', value: 20),
|
||||
SelectionPopupModel(title: '30 минут', value: 30),
|
||||
SelectionPopupModel(title: '1 час', value: 60),
|
||||
SelectionPopupModel(title: '2 часа', value: 120),
|
||||
SelectionPopupModel(title: '10 дней', value: 14400),
|
||||
];
|
||||
final List<SelectionPopupModel> statuses = [
|
||||
SelectionPopupModel(title: 'Активна', value: 'active'),
|
||||
SelectionPopupModel(title: 'Неактивна', value: 'inactive'),
|
||||
];
|
||||
final selectedType = types.firstWhere(
|
||||
(type) => type.value == recipe.type,
|
||||
orElse: () => types[0],
|
||||
);
|
||||
|
||||
final selectedCookingTime = cookingTimes.firstWhere(
|
||||
(time) => time.value == recipe.cooking_time,
|
||||
orElse: () => cookingTimes[0],
|
||||
);
|
||||
|
||||
final selectedStatus = statuses.firstWhere(
|
||||
(status) => status.value == recipe.state,
|
||||
orElse: () => statuses[0],
|
||||
);
|
||||
|
||||
final stepControllers = recipe.steps?.map((step) {
|
||||
return TextEditingController(text: step?.description);
|
||||
}).toList();
|
||||
|
||||
final stepImages = recipe.steps?.map((step) {
|
||||
return step?.image != null ? XFile(step!.image!) : null;
|
||||
}).toList();
|
||||
|
||||
final ingredients = recipe.ingredients?.map((ingredient) {
|
||||
return Ingredient(
|
||||
name: ingredient!.name,
|
||||
portion: ingredient.portion,
|
||||
unit: ingredient.unit,
|
||||
);
|
||||
}).toList();
|
||||
|
||||
emit(RecipeCreateState.loaded(
|
||||
picker: ImagePicker(),
|
||||
recipeImage: XFile(recipe.image!),
|
||||
titleController: TextEditingController(text: recipe.title),
|
||||
productNameController: TextEditingController(),
|
||||
ingredientController: TextEditingController(),
|
||||
stepDescriptionController: TextEditingController(text: recipe.description),
|
||||
videoLinkController: TextEditingController(text: recipe.video_url),
|
||||
stepControllers: stepControllers ?? [],
|
||||
stepImages: stepImages ?? [],
|
||||
autocompleteList: [],
|
||||
ingredients: ingredients ?? [],
|
||||
numberOfSteps: 1,
|
||||
ingredientAsItems: false,
|
||||
types: types,
|
||||
cookingTimes: cookingTimes,
|
||||
statuses: statuses,
|
||||
selectedType: selectedType,
|
||||
selectedCookingTime: selectedCookingTime,
|
||||
selectedStatus: selectedStatus,),);
|
||||
} catch (e) {
|
||||
emit(RecipeCreateState.error(errorMessage: e.toString()));
|
||||
}
|
||||
}
|
||||
|
||||
loadRecipeCreate() {
|
||||
emit(const RecipeCreateStateLoading());
|
||||
try {
|
||||
final List<SelectionPopupModel> types = [
|
||||
SelectionPopupModel(title: 'Первые блюда', value: 'first_meals'),
|
||||
SelectionPopupModel(title: 'Вторые блюда', value: 'second_meals'),
|
||||
SelectionPopupModel(title: 'Cалаты', value: 'salads'),
|
||||
SelectionPopupModel(title: 'Перекусы/закуски', value: 'snacks'),
|
||||
SelectionPopupModel(title: 'Сладости', value: 'sweets'),
|
||||
SelectionPopupModel(title: 'Напитки', value: 'drinks'),
|
||||
SelectionPopupModel(title: 'Прочее', value: 'other'),
|
||||
];
|
||||
final List<SelectionPopupModel> cookingTimes = [
|
||||
SelectionPopupModel(title: '10 минут', value: 10),
|
||||
SelectionPopupModel(title: '20 минут', value: 20),
|
||||
SelectionPopupModel(title: '30 минут', value: 30),
|
||||
SelectionPopupModel(title: '1 час', value: 60),
|
||||
SelectionPopupModel(title: '2 часа', value: 120),
|
||||
SelectionPopupModel(title: '10 дней', value: 14400),
|
||||
];
|
||||
final List<SelectionPopupModel> statuses = [
|
||||
SelectionPopupModel(title: 'Активна', value: 'active'),
|
||||
SelectionPopupModel(title: 'Неактивна', value: 'inactive'),
|
||||
];
|
||||
emit(RecipeCreateState.loaded(
|
||||
picker: ImagePicker(),
|
||||
titleController: TextEditingController(),
|
||||
productNameController: TextEditingController(),
|
||||
ingredientController: TextEditingController(),
|
||||
stepDescriptionController: TextEditingController(),
|
||||
videoLinkController: TextEditingController(),
|
||||
stepControllers: [TextEditingController()],
|
||||
stepImages: [null],
|
||||
autocompleteList: [],
|
||||
ingredients: [],
|
||||
numberOfSteps: 1,
|
||||
ingredientAsItems: false,
|
||||
types: types,
|
||||
cookingTimes: cookingTimes,
|
||||
statuses: statuses,
|
||||
selectedType: types[0],
|
||||
selectedCookingTime: cookingTimes[0],
|
||||
selectedStatus: statuses[0],),);
|
||||
} catch (e) {
|
||||
emit(RecipeCreateState.error(errorMessage: e.toString()));
|
||||
}
|
||||
}
|
||||
|
||||
publishRecipe(RecipeModel recipe) async {
|
||||
await recipeRepository.publishRecipe(recipe);
|
||||
}
|
||||
|
||||
editRecipe(RecipeModel recipe, String id) async {
|
||||
await recipeRepository.editRecipe(recipe, id);
|
||||
}
|
||||
|
||||
uploadRecipeImage(XFile? pickedFile) {
|
||||
try {
|
||||
if (state is RecipeCreateStateLoaded) {
|
||||
var currentState = state as RecipeCreateStateLoaded;
|
||||
var updatedState = currentState.copyWith(
|
||||
recipeImage: pickedFile,
|
||||
);
|
||||
emit(updatedState);
|
||||
}
|
||||
} catch (e) {
|
||||
emit(RecipeCreateState.error(errorMessage: e.toString()));
|
||||
}
|
||||
}
|
||||
|
||||
uploadStepImage(XFile? pickedFile, int stepNumber) {
|
||||
try {
|
||||
if (state is RecipeCreateStateLoaded) {
|
||||
var currentState = state as RecipeCreateStateLoaded;
|
||||
|
||||
var updatedStepImages = List<XFile?>.from(currentState.stepImages);
|
||||
|
||||
if (stepNumber >= 0 && stepNumber < updatedStepImages.length) {
|
||||
updatedStepImages[stepNumber] = pickedFile;
|
||||
}
|
||||
|
||||
var updatedState = currentState.copyWith(
|
||||
stepImages: updatedStepImages,
|
||||
);
|
||||
|
||||
emit(updatedState);
|
||||
}
|
||||
} catch (e) {
|
||||
emit(RecipeCreateState.error(errorMessage: e.toString()));
|
||||
}
|
||||
}
|
||||
|
||||
switchUnits(bool value) {
|
||||
try {
|
||||
if (state is RecipeCreateStateLoaded) {
|
||||
var currentState = state as RecipeCreateStateLoaded;
|
||||
var updatedState = currentState.copyWith(ingredientAsItems: value);
|
||||
emit(updatedState);
|
||||
}
|
||||
} catch (e) {
|
||||
emit(RecipeCreateState.error(errorMessage: e.toString()));
|
||||
}
|
||||
}
|
||||
|
||||
addIngredient(Ingredient ingredient) {
|
||||
try {
|
||||
if (state is RecipeCreateStateLoaded) {
|
||||
var currentState = state as RecipeCreateStateLoaded;
|
||||
|
||||
bool ingredientExists = currentState.ingredients.any((existingIngredient) => existingIngredient.name == ingredient.name);
|
||||
|
||||
if (ingredientExists) {
|
||||
emit(RecipeCreateState.error(errorMessage: 'Ингредиент уже существует.'));
|
||||
return;
|
||||
}
|
||||
|
||||
List<Ingredient> newIngredients = List<Ingredient>.from(currentState.ingredients);
|
||||
newIngredients.add(ingredient);
|
||||
|
||||
var updatedState = currentState.copyWith(ingredients: newIngredients);
|
||||
emit(updatedState);
|
||||
}
|
||||
} catch (e) {
|
||||
emit(RecipeCreateState.error(errorMessage: e.toString()));
|
||||
}
|
||||
}
|
||||
|
||||
addStep() {
|
||||
try {
|
||||
if (state is RecipeCreateStateLoaded) {
|
||||
var currentState = state as RecipeCreateStateLoaded;
|
||||
List<XFile?> newStepImages = List<XFile?>.from(currentState.stepImages);
|
||||
newStepImages.add(null);
|
||||
List<TextEditingController> newStepControllers = List<TextEditingController>.from(currentState.stepControllers);
|
||||
newStepControllers.add(TextEditingController());
|
||||
var updatedState = currentState.copyWith(stepImages: newStepImages, stepControllers: newStepControllers, numberOfSteps: currentState.numberOfSteps + 1);
|
||||
emit(updatedState);
|
||||
}
|
||||
} catch (e) {
|
||||
emit(RecipeCreateState.error(errorMessage: e.toString()));
|
||||
}
|
||||
}
|
||||
|
||||
removeStep() {
|
||||
try {
|
||||
if (state is RecipeCreateStateLoaded) {
|
||||
var currentState = state as RecipeCreateStateLoaded;
|
||||
List<XFile?> newStepImages = List<XFile?>.from(currentState.stepImages);
|
||||
newStepImages.removeLast();
|
||||
List<TextEditingController> newStepControllers = List<TextEditingController>.from(currentState.stepControllers);
|
||||
newStepControllers.removeLast();
|
||||
var updatedState = currentState.copyWith(stepImages: newStepImages, stepControllers: newStepControllers, numberOfSteps: currentState.numberOfSteps - 1);
|
||||
emit(updatedState);
|
||||
}
|
||||
} catch (e) {
|
||||
emit(RecipeCreateState.error(errorMessage: e.toString()));
|
||||
}
|
||||
}
|
||||
|
||||
inputProduct(String input) async {
|
||||
if (state is RecipeCreateStateLoaded) {
|
||||
var currentState = state as RecipeCreateStateLoaded;
|
||||
|
||||
final Map<String, List<Product>> productNutritionalValues = await recipeRepository.inputProduct(input);
|
||||
|
||||
var updatedState = currentState.copyWith(
|
||||
productNameController: TextEditingController(text: input),
|
||||
autocompleteList: productNutritionalValues['product_nutritional_value'] ?? [],
|
||||
);
|
||||
|
||||
emit(updatedState);
|
||||
}
|
||||
}
|
||||
|
||||
changeType(SelectionPopupModel type) {
|
||||
if (state is RecipeCreateStateLoaded) {
|
||||
var currentState = state as RecipeCreateStateLoaded;
|
||||
RecipeCreateStateLoaded updatedState = currentState.copyWith(
|
||||
selectedType: type,
|
||||
);
|
||||
emit(updatedState);
|
||||
}
|
||||
}
|
||||
|
||||
changeStatus(SelectionPopupModel status) {
|
||||
if (state is RecipeCreateStateLoaded) {
|
||||
var currentState = state as RecipeCreateStateLoaded;
|
||||
RecipeCreateStateLoaded updatedState = currentState.copyWith(
|
||||
selectedStatus: status,
|
||||
);
|
||||
emit(updatedState);
|
||||
}
|
||||
}
|
||||
|
||||
changeCookingTime(SelectionPopupModel cookingTime) {
|
||||
if (state is RecipeCreateStateLoaded) {
|
||||
var currentState = state as RecipeCreateStateLoaded;
|
||||
RecipeCreateStateLoaded updatedState = currentState.copyWith(
|
||||
selectedCookingTime: cookingTime,
|
||||
);
|
||||
emit(updatedState);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,42 +0,0 @@
|
||||
import 'package:eatvio_app/core/data/selectionPopupModel/selection_popup_model.dart';
|
||||
import 'package:eatvio_app/features/recipes/models/recipe_model.dart';
|
||||
import 'package:eatvio_app/features/recipes/models/ingredient.dart';
|
||||
import 'package:eatvio_app/features/recipes/models/product.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
import 'package:image_picker/image_picker.dart';
|
||||
part 'recipe_create_state.freezed.dart';
|
||||
|
||||
@freezed
|
||||
abstract class RecipeCreateState with _$RecipeCreateState {
|
||||
const factory RecipeCreateState() = _RecipeCreateState;
|
||||
|
||||
const factory RecipeCreateState.loading() = RecipeCreateStateLoading;
|
||||
|
||||
const factory RecipeCreateState.loaded({
|
||||
RecipeModel? recipe,
|
||||
required ImagePicker picker,
|
||||
required TextEditingController titleController,
|
||||
required TextEditingController productNameController,
|
||||
required TextEditingController ingredientController,
|
||||
required TextEditingController stepDescriptionController,
|
||||
required TextEditingController videoLinkController,
|
||||
required List<TextEditingController> stepControllers,
|
||||
XFile? recipeImage,
|
||||
required List<XFile?> stepImages,
|
||||
required List<Ingredient> ingredients,
|
||||
required List<Product> autocompleteList,
|
||||
required int numberOfSteps,
|
||||
required bool ingredientAsItems,
|
||||
required List<SelectionPopupModel> types,
|
||||
required List<SelectionPopupModel> cookingTimes,
|
||||
required List<SelectionPopupModel> statuses,
|
||||
required SelectionPopupModel selectedType,
|
||||
required SelectionPopupModel selectedCookingTime,
|
||||
required SelectionPopupModel selectedStatus,
|
||||
}) = RecipeCreateStateLoaded;
|
||||
|
||||
const factory RecipeCreateState.error({
|
||||
required String errorMessage,
|
||||
}) = RecipeCreateStateError;
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,508 +1,71 @@
|
||||
import 'dart:io';
|
||||
import 'package:eatvio_app/core/theme/app_decoration.dart';
|
||||
import 'package:eatvio_app/core/widgets/custom_drop_down.dart';
|
||||
import 'package:eatvio_app/core/widgets/loader.dart';
|
||||
import 'package:eatvio_app/features/recipes/models/product.dart';
|
||||
import 'package:eatvio_app/features/recipes/models/ingredient.dart';
|
||||
import 'package:eatvio_app/features/recipes/models/recipe_model.dart';
|
||||
import 'package:eatvio_app/features/recipes/models/step.dart' as step;
|
||||
import 'package:eatvio_app/features/recipes/presentation/bloc/recipe_create_cubit.dart';
|
||||
import 'package:eatvio_app/features/recipes/presentation/bloc/recipe_create_state.dart';
|
||||
import 'package:eatvio_app/core/locator_service.dart';
|
||||
import 'package:eatvio_app/core/theme/custom_text_style.dart';
|
||||
import 'package:eatvio_app/core/theme/theme_helper.dart';
|
||||
import 'package:eatvio_app/core/widgets/custom_text_form_field.dart';
|
||||
import 'package:eatvio_app/features/recipes/presentation/bloc/create_recipe_cubit/create_recipe_cubit.dart';
|
||||
import 'package:eatvio_app/features/recipes/presentation/widgets/info_about_recipe_widget.dart';
|
||||
import 'package:eatvio_app/features/recipes/presentation/widgets/ingredients_widget.dart';
|
||||
import 'package:eatvio_app/features/recipes/presentation/widgets/steps_widget.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:image_picker/image_picker.dart';
|
||||
|
||||
class RecipeCreateScreen extends StatelessWidget {
|
||||
RecipeCreateScreen({Key? key}) : super(key: key);
|
||||
class RecipeCreateScreen extends StatefulWidget {
|
||||
const RecipeCreateScreen({super.key});
|
||||
|
||||
@override
|
||||
State<RecipeCreateScreen> createState() => _RecipeCreateScreenState();
|
||||
}
|
||||
|
||||
class _RecipeCreateScreenState extends State<RecipeCreateScreen> {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return BlocProvider<RecipeCreateCubit>(
|
||||
create: (context) => sl<RecipeCreateCubit>()..loadRecipeCreate(),
|
||||
child: BlocBuilder<RecipeCreateCubit, RecipeCreateState>(
|
||||
builder: (context, state) {
|
||||
if (state is RecipeCreateStateLoaded) {
|
||||
return _buildRecipeCreate(context, state);
|
||||
} else if (state is RecipeCreateStateLoading) {
|
||||
return Scaffold(
|
||||
body: const Loader(),
|
||||
);
|
||||
} else if (state is RecipeCreateStateError) {
|
||||
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 _buildRecipeCreate(BuildContext context, RecipeCreateStateLoaded state) {
|
||||
List<String> _options = state.autocompleteList.map((Product item) => item.name).toList();
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
actions: [
|
||||
InkWell(
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
child: Ink(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16),
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
SizedBox(height: 4),
|
||||
Text(
|
||||
'Новый рецепт',
|
||||
style: TextStyle(
|
||||
color: Colors.black,
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.w400,
|
||||
height: 1,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
return BlocProvider<CreateRecipeCubit>(
|
||||
create: (BuildContext context) => sl<CreateRecipeCubit>(),
|
||||
child: Scaffold(
|
||||
backgroundColor: Color(0xFFF0F2F5),
|
||||
appBar: AppBar(
|
||||
title: Text('Добавление рецепта'),
|
||||
centerTitle: true,
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.vertical(
|
||||
bottom: Radius.circular(12),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
backgroundColor: Color.fromARGB(255, 240, 242, 245),
|
||||
resizeToAvoidBottomInset: true,
|
||||
body: SizedBox(
|
||||
width: double.maxFinite,
|
||||
child: SingleChildScrollView(
|
||||
),
|
||||
body: SingleChildScrollView(
|
||||
physics: const BouncingScrollPhysics(),
|
||||
padding: EdgeInsets.only(top: 8, bottom: 32),
|
||||
child: Column(
|
||||
spacing: 16,
|
||||
children: [
|
||||
Container(
|
||||
padding: EdgeInsets.symmetric(
|
||||
horizontal: 24,
|
||||
vertical: 12,
|
||||
),
|
||||
InfoAboutRecipeWidget(),
|
||||
IngredientsWidget(),
|
||||
StepsWidget(),
|
||||
InkWell(
|
||||
onTap: () {},
|
||||
borderRadius: BorderRadius.circular(20),
|
||||
child: Ink(
|
||||
width: MediaQuery.of(context).size.width - 32,
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white,
|
||||
borderRadius: const BorderRadius.all(Radius.circular(8)),
|
||||
color: appTheme.blueA400,
|
||||
borderRadius: BorderRadius.circular(20),
|
||||
),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text('Заголовок'),
|
||||
SizedBox(height: 8),
|
||||
CustomTextFormField(
|
||||
fillColor: appTheme.gray50,
|
||||
controller: state.titleController,
|
||||
hintText: 'Заголовок',
|
||||
child: Container(
|
||||
padding: const EdgeInsets.all(16),
|
||||
alignment: Alignment.center,
|
||||
child: Text(
|
||||
'Опубликовать',
|
||||
style: TextStyle(
|
||||
fontSize: 16,
|
||||
color: Colors.white,
|
||||
fontWeight: FontWeight.w600,
|
||||
),
|
||||
SizedBox(height: 16),
|
||||
Text('Тип блюда'),
|
||||
SizedBox(height: 8),
|
||||
CustomDropDown(
|
||||
value: state.selectedType,
|
||||
items: state.types,
|
||||
onChanged: changeType(context),
|
||||
),
|
||||
SizedBox(height: 16),
|
||||
Text('Изображение'),
|
||||
GestureDetector(
|
||||
onTap: () async {
|
||||
final pickedFile = await state.picker.pickImage(source: ImageSource.gallery);
|
||||
context.read<RecipeCreateCubit>().uploadRecipeImage(pickedFile);
|
||||
},
|
||||
child: state.recipeImage == null
|
||||
? Container(
|
||||
height: 128,
|
||||
decoration: AppDecoration.fillGray.copyWith(
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
Icon(
|
||||
Icons.photo_library,
|
||||
color: appTheme.blueGray300,
|
||||
),
|
||||
SizedBox(width: 8),
|
||||
Text(
|
||||
"Добавить фото",
|
||||
selectionColor: appTheme.blueA400,
|
||||
),
|
||||
],
|
||||
),
|
||||
)
|
||||
: Image.file(File(state.recipeImage!.path), height: 200, fit: BoxFit.cover),
|
||||
),
|
||||
SizedBox(height: 16),
|
||||
Text('Время приготовления'),
|
||||
SizedBox(height: 8),
|
||||
CustomDropDown(
|
||||
items: state.cookingTimes,
|
||||
value: state.selectedCookingTime,
|
||||
onChanged: changeCookingTime(context),
|
||||
),
|
||||
SizedBox(height: 16),
|
||||
Text('Статус'),
|
||||
SizedBox(height: 8),
|
||||
CustomDropDown(
|
||||
items: state.statuses,
|
||||
value: state.selectedStatus,
|
||||
onChanged: changeStatus(context),
|
||||
),
|
||||
SizedBox(height: 24),
|
||||
if (!state.ingredients.isEmpty)
|
||||
for (int j = 0; j < state.ingredients.length; j++) _buildIngredient(state.ingredients[j]),
|
||||
SizedBox(height: 24),
|
||||
RawAutocomplete<String>(
|
||||
optionsBuilder: _buildOptions(_options),
|
||||
fieldViewBuilder: (
|
||||
BuildContext context,
|
||||
TextEditingController productEditingController,
|
||||
FocusNode focusNodeName,
|
||||
VoidCallback onFieldSubmitted,
|
||||
) {
|
||||
return CustomTextFormField(
|
||||
hintText: 'Введите название продукта',
|
||||
controller: TextEditingController(text: state.productNameController.text),
|
||||
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: 24),
|
||||
CustomTextFormField(
|
||||
hintText: 'Порция',
|
||||
fillColor: appTheme.gray50,
|
||||
controller: state.ingredientController,
|
||||
keyboardType: TextInputType.number,
|
||||
inputFormatters: <TextInputFormatter>[FilteringTextInputFormatter.digitsOnly],
|
||||
),
|
||||
SizedBox(height: 8),
|
||||
Row(children: [
|
||||
Text('гр'),
|
||||
Switch(
|
||||
value: state.ingredientAsItems,
|
||||
onChanged: (bool value) => context.read<RecipeCreateCubit>().switchUnits(value),
|
||||
activeColor: appTheme.blueA400,
|
||||
inactiveThumbColor: Colors.grey,
|
||||
inactiveTrackColor: Colors.grey.shade300,
|
||||
),
|
||||
Text('шт'),
|
||||
],),
|
||||
SizedBox(height: 8),
|
||||
Container(
|
||||
height: 48,
|
||||
width: MediaQuery.of(context).size.width,
|
||||
child: ElevatedButton(
|
||||
style: ElevatedButton.styleFrom(
|
||||
elevation: 0,
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
),
|
||||
backgroundColor: appTheme.blueA400,
|
||||
),
|
||||
onPressed: () => context.read<RecipeCreateCubit>().addIngredient(
|
||||
Ingredient(
|
||||
name: state.productNameController.text,
|
||||
portion: state.ingredientController.text,
|
||||
unit: state.ingredientAsItems ? 'pcs' : 'gr',
|
||||
),
|
||||
),
|
||||
child: Text(
|
||||
'+',
|
||||
style: theme.textTheme.titleSmall,
|
||||
),
|
||||
),
|
||||
),
|
||||
SizedBox(height: 24),
|
||||
Text('Описание рецепта'),
|
||||
SizedBox(height: 8),
|
||||
CustomTextFormField(
|
||||
maxLines: 8,
|
||||
minLines: 8,
|
||||
controller: state.stepDescriptionController,
|
||||
),
|
||||
SizedBox(height: 24),
|
||||
for (int i = 0; i < state.numberOfSteps; i++) _buildStep(context, i, state),
|
||||
SizedBox(height: 24),
|
||||
Container(
|
||||
height: 48,
|
||||
width: MediaQuery.of(context).size.width,
|
||||
child: ElevatedButton(
|
||||
style: ElevatedButton.styleFrom(
|
||||
elevation: 0,
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
),
|
||||
backgroundColor: appTheme.blueA400,
|
||||
),
|
||||
onPressed: () => context.read<RecipeCreateCubit>().addStep(),
|
||||
child: Text(
|
||||
'+',
|
||||
style: theme.textTheme.titleSmall,
|
||||
),
|
||||
),
|
||||
),
|
||||
SizedBox(height: 8),
|
||||
Container(
|
||||
height: 48,
|
||||
width: MediaQuery.of(context).size.width,
|
||||
child: ElevatedButton(
|
||||
style: ElevatedButton.styleFrom(
|
||||
elevation: 0,
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
),
|
||||
backgroundColor: Colors.grey,
|
||||
),
|
||||
onPressed: () => context.read<RecipeCreateCubit>().removeStep(),
|
||||
child: Text(
|
||||
'-',
|
||||
style: theme.textTheme.titleSmall,
|
||||
),
|
||||
),
|
||||
),
|
||||
SizedBox(height: 24),
|
||||
Text('Ссылка на видео (video)'),
|
||||
SizedBox(height: 8),
|
||||
CustomTextFormField(
|
||||
hintText: 'Введите описание шага',
|
||||
fillColor: appTheme.gray50,
|
||||
controller: state.videoLinkController,
|
||||
),
|
||||
SizedBox(height: 24),
|
||||
Container(
|
||||
height: 48,
|
||||
width: MediaQuery.of(context).size.width,
|
||||
child: ElevatedButton(
|
||||
style: ElevatedButton.styleFrom(
|
||||
elevation: 0,
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
),
|
||||
backgroundColor: appTheme.blueA400,
|
||||
),
|
||||
child: Text(
|
||||
'Опубликовать',
|
||||
style: theme.textTheme.titleSmall,
|
||||
),
|
||||
onPressed: () => context.read<RecipeCreateCubit>().publishRecipe(createRecipeFromState(state)),
|
||||
),
|
||||
),
|
||||
],
|
||||
),),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
_buildStep(BuildContext context, int stepNumber, RecipeCreateStateLoaded state) {
|
||||
return Column(
|
||||
children: [
|
||||
Row(children: [Text('Шаг ${stepNumber + 1}'), Spacer(), Text('Всего шагов: ${state.numberOfSteps}')]),
|
||||
SizedBox(height: 8),
|
||||
GestureDetector(
|
||||
onTap: () async {
|
||||
final pickedFile = await state.picker.pickImage(source: ImageSource.gallery);
|
||||
context.read<RecipeCreateCubit>().uploadStepImage(pickedFile, stepNumber);
|
||||
},
|
||||
child: state.stepImages[stepNumber] == null
|
||||
? Container(
|
||||
height: 128,
|
||||
decoration: AppDecoration.fillGray.copyWith(
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
Icon(
|
||||
Icons.photo_library,
|
||||
color: appTheme.blueGray300,
|
||||
),
|
||||
SizedBox(width: 8),
|
||||
Text(
|
||||
"Добавить фото",
|
||||
selectionColor: appTheme.blueA400,
|
||||
),
|
||||
SizedBox(height: 8),
|
||||
],
|
||||
),
|
||||
)
|
||||
: Image.file(File(state.stepImages[stepNumber]!.path), height: 150, fit: BoxFit.cover),),
|
||||
SizedBox(height: 16),
|
||||
CustomTextFormField(
|
||||
hintText: 'Введите описание шага',
|
||||
maxLines: 8,
|
||||
minLines: 8,
|
||||
fillColor: appTheme.gray50,
|
||||
controller: state.stepControllers[stepNumber],
|
||||
),
|
||||
SizedBox(height: 24),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
_buildIngredient(Ingredient ingredient) {
|
||||
return Container(
|
||||
margin: EdgeInsets.symmetric(horizontal: 12),
|
||||
padding: EdgeInsets.symmetric(
|
||||
horizontal: 8,
|
||||
vertical: 8,
|
||||
),
|
||||
decoration: AppDecoration.fillGray.copyWith(
|
||||
borderRadius: BorderRadiusStyle.roundedBorder5,
|
||||
),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.end,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Padding(
|
||||
padding: EdgeInsets.only(
|
||||
left: 8,
|
||||
top: 8,
|
||||
),
|
||||
child: Text(
|
||||
ingredient.name.length > 8 ? ingredient.name.substring(0, 8) : ingredient.name,
|
||||
style: CustomTextStyles.titleMedium16,
|
||||
),
|
||||
),
|
||||
Padding(
|
||||
padding: EdgeInsets.only(
|
||||
left: 24,
|
||||
top: 4,
|
||||
bottom: 4,
|
||||
),
|
||||
child: Text(
|
||||
ingredient.portion.toString() + ' ' + ingredient.unit.toString(),
|
||||
),
|
||||
),
|
||||
Spacer(),
|
||||
GestureDetector(
|
||||
// onTap: removeIngredient(context, ingredient),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.only(right: 10),
|
||||
child: Icon(
|
||||
Icons.close,
|
||||
color: appTheme.blueGray300,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
_buildOptions(List<String> _options) {
|
||||
return (TextEditingValue textEditingValue) {
|
||||
return _options.where((String option) {
|
||||
return true;
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
selectProduct(AutocompleteOnSelected<String> onSelected, String option, RecipeCreateStateLoaded state) {
|
||||
state.productNameController.text = option;
|
||||
return () {
|
||||
onSelected(option);
|
||||
};
|
||||
}
|
||||
|
||||
getAutocomplete(BuildContext context, FocusNode focusNode) {
|
||||
return (value) {
|
||||
if (value.length > 2) {
|
||||
context.read<RecipeCreateCubit>().inputProduct(value);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
changeType(BuildContext context) {
|
||||
return (value) {
|
||||
context.read<RecipeCreateCubit>().changeType(value);
|
||||
};
|
||||
}
|
||||
|
||||
changeStatus(BuildContext context) {
|
||||
return (value) {
|
||||
context.read<RecipeCreateCubit>().changeStatus(value);
|
||||
};
|
||||
}
|
||||
|
||||
changeCookingTime(BuildContext context) {
|
||||
return (value) {
|
||||
context.read<RecipeCreateCubit>().changeCookingTime(value);
|
||||
};
|
||||
}
|
||||
|
||||
RecipeModel createRecipeFromState(RecipeCreateStateLoaded state) {
|
||||
List<Ingredient> ingredients = state.ingredients;
|
||||
|
||||
List<step.Step> steps = [];
|
||||
for (int i = 0; i < state.stepControllers.length; i++) {
|
||||
if (state.stepControllers[i].text.isNotEmpty) {
|
||||
steps.add(step.Step(
|
||||
description: state.stepControllers[i].text,
|
||||
image: state.stepImages[i]?.path,
|
||||
),);
|
||||
}
|
||||
}
|
||||
|
||||
final RecipeModel recipe = RecipeModel(
|
||||
ingredients: ingredients,
|
||||
steps: steps,
|
||||
state: state.selectedStatus.value,
|
||||
type: state.selectedType.value,
|
||||
cooking_time: state.selectedCookingTime.value,
|
||||
description: state.stepDescriptionController.text,
|
||||
title: state.titleController.text,
|
||||
image: state.recipeImage?.path,
|
||||
video_url: state.videoLinkController.text.isNotEmpty ? state.videoLinkController.text : null,
|
||||
);
|
||||
|
||||
return recipe;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,516 +0,0 @@
|
||||
import 'package:eatvio_app/core/const.dart';
|
||||
import 'package:eatvio_app/core/theme/app_decoration.dart';
|
||||
import 'package:eatvio_app/core/widgets/custom_drop_down.dart';
|
||||
import 'package:eatvio_app/core/widgets/loader.dart';
|
||||
import 'package:eatvio_app/features/recipes/models/product.dart';
|
||||
import 'package:eatvio_app/features/recipes/models/ingredient.dart';
|
||||
import 'package:eatvio_app/features/recipes/models/recipe_model.dart';
|
||||
import 'package:eatvio_app/features/recipes/models/step.dart' as step;
|
||||
import 'package:eatvio_app/features/recipes/presentation/bloc/recipe_create_cubit.dart';
|
||||
import 'package:eatvio_app/features/recipes/presentation/bloc/recipe_create_state.dart';
|
||||
import 'package:eatvio_app/core/theme/custom_text_style.dart';
|
||||
import 'package:eatvio_app/core/theme/theme_helper.dart';
|
||||
import 'package:eatvio_app/core/widgets/custom_text_form_field.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:image_picker/image_picker.dart';
|
||||
|
||||
// ignore: must_be_immutable
|
||||
class RecipeEditScreen extends StatelessWidget {
|
||||
final String recipeID;
|
||||
|
||||
const RecipeEditScreen({
|
||||
required this.recipeID,
|
||||
super.key,
|
||||
});
|
||||
|
||||
Widget build(BuildContext context) {
|
||||
if (recipeID != "0") {
|
||||
context.read<RecipeCreateCubit>().loadRecipeById(recipeID);
|
||||
}
|
||||
return BlocBuilder<RecipeCreateCubit, RecipeCreateState>(
|
||||
builder: (context, state) {
|
||||
if (state is RecipeCreateStateLoaded) {
|
||||
return _buildRecipeCreate(context, state);
|
||||
} else if (state is RecipeCreateStateLoading) {
|
||||
return Scaffold(
|
||||
body: Loader(),
|
||||
);
|
||||
} else if (state is RecipeCreateStateError) {
|
||||
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 _buildRecipeCreate(BuildContext context, RecipeCreateStateLoaded state) {
|
||||
List<String> _options = state.autocompleteList.map((Product item) => item.name).toList();
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
actions: [
|
||||
InkWell(
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
child: Ink(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16),
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
SizedBox(height: 4),
|
||||
Text(
|
||||
state.titleController.text,
|
||||
style: TextStyle(
|
||||
color: Colors.black,
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.w400,
|
||||
height: 1,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
backgroundColor: Color.fromARGB(255, 240, 242, 245),
|
||||
resizeToAvoidBottomInset: true,
|
||||
body: SizedBox(
|
||||
width: double.maxFinite,
|
||||
child: SingleChildScrollView(
|
||||
child: Column(
|
||||
children: [
|
||||
Container(
|
||||
padding: EdgeInsets.symmetric(
|
||||
horizontal: 24,
|
||||
vertical: 12,
|
||||
),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white,
|
||||
borderRadius: const BorderRadius.all(Radius.circular(8)),
|
||||
),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text('Заголовок'),
|
||||
SizedBox(height: 8),
|
||||
CustomTextFormField(
|
||||
fillColor: appTheme.gray50,
|
||||
controller: state.titleController,
|
||||
hintText: 'Заголовок',
|
||||
),
|
||||
SizedBox(height: 16),
|
||||
Text('Тип блюда'),
|
||||
SizedBox(height: 8),
|
||||
CustomDropDown(
|
||||
value: state.selectedType,
|
||||
items: state.types,
|
||||
onChanged: changeType(context),
|
||||
),
|
||||
SizedBox(height: 16),
|
||||
Text('Изображение'),
|
||||
GestureDetector(
|
||||
onTap: () async {
|
||||
final pickedFile = await state.picker.pickImage(source: ImageSource.gallery);
|
||||
context.read<RecipeCreateCubit>().uploadRecipeImage(pickedFile);
|
||||
},
|
||||
child: state.recipeImage == null
|
||||
? Container(
|
||||
height: 128,
|
||||
decoration: AppDecoration.fillGray.copyWith(
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
Icon(
|
||||
Icons.photo_library,
|
||||
color: appTheme.blueGray300,
|
||||
),
|
||||
SizedBox(width: 8),
|
||||
Text(
|
||||
"Добавить фото",
|
||||
selectionColor: appTheme.blueA400,
|
||||
),
|
||||
],
|
||||
),
|
||||
)
|
||||
: Image.network(
|
||||
baseUrl + '/storage/' + state.recipeImage!.path,
|
||||
height: 150,
|
||||
fit: BoxFit.cover,
|
||||
),
|
||||
),
|
||||
SizedBox(height: 16),
|
||||
Text('Время приготовления'),
|
||||
SizedBox(height: 8),
|
||||
CustomDropDown(
|
||||
items: state.cookingTimes,
|
||||
value: state.selectedCookingTime,
|
||||
onChanged: changeCookingTime(context),
|
||||
),
|
||||
SizedBox(height: 16),
|
||||
Text('Статус'),
|
||||
SizedBox(height: 8),
|
||||
CustomDropDown(
|
||||
items: state.statuses,
|
||||
value: state.selectedStatus,
|
||||
onChanged: changeStatus(context),
|
||||
),
|
||||
SizedBox(height: 24),
|
||||
if (!state.ingredients.isEmpty)
|
||||
for (int j = 0; j < state.ingredients.length; j++) _buildIngredient(state.ingredients[j]),
|
||||
SizedBox(height: 24),
|
||||
RawAutocomplete<String>(
|
||||
optionsBuilder: _buildOptions(_options),
|
||||
fieldViewBuilder: (
|
||||
BuildContext context,
|
||||
TextEditingController productEditingController,
|
||||
FocusNode focusNodeName,
|
||||
VoidCallback onFieldSubmitted,
|
||||
) {
|
||||
return CustomTextFormField(
|
||||
hintText: 'Введите название продукта',
|
||||
controller: TextEditingController(text: state.productNameController.text),
|
||||
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: 24),
|
||||
CustomTextFormField(
|
||||
hintText: 'Порция',
|
||||
fillColor: appTheme.gray50,
|
||||
controller: state.ingredientController,
|
||||
keyboardType: TextInputType.number,
|
||||
inputFormatters: <TextInputFormatter>[FilteringTextInputFormatter.digitsOnly],
|
||||
),
|
||||
SizedBox(height: 8),
|
||||
Row(children: [
|
||||
Text('гр'),
|
||||
Switch(
|
||||
value: state.ingredientAsItems,
|
||||
onChanged: (bool value) => context.read<RecipeCreateCubit>().switchUnits(value),
|
||||
activeColor: appTheme.blueA400,
|
||||
inactiveThumbColor: Colors.grey,
|
||||
inactiveTrackColor: Colors.grey.shade300,
|
||||
),
|
||||
Text('шт'),
|
||||
],),
|
||||
SizedBox(height: 8),
|
||||
Container(
|
||||
height: 48,
|
||||
width: MediaQuery.of(context).size.width,
|
||||
child: ElevatedButton(
|
||||
style: ElevatedButton.styleFrom(
|
||||
elevation: 0,
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
),
|
||||
backgroundColor: appTheme.blueA400,
|
||||
),
|
||||
child: Text(
|
||||
'+',
|
||||
style: theme.textTheme.titleSmall,
|
||||
),
|
||||
onPressed: () => context.read<RecipeCreateCubit>().addIngredient(new Ingredient(
|
||||
name: state.productNameController.text,
|
||||
portion: state.ingredientController.text,
|
||||
unit: state.ingredientAsItems ? 'pcs' : 'gr',),),),
|
||||
),
|
||||
SizedBox(height: 24),
|
||||
Text('Описание рецепта'),
|
||||
SizedBox(height: 8),
|
||||
CustomTextFormField(
|
||||
maxLines: 8,
|
||||
minLines: 8,
|
||||
controller: state.stepDescriptionController,
|
||||
),
|
||||
SizedBox(height: 24),
|
||||
for (int i = 0; i < state.numberOfSteps; i++) _buildStep(context, i, state),
|
||||
SizedBox(height: 24),
|
||||
Container(
|
||||
height: 48,
|
||||
width: MediaQuery.of(context).size.width,
|
||||
child: ElevatedButton(
|
||||
style: ElevatedButton.styleFrom(
|
||||
elevation: 0,
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
),
|
||||
backgroundColor: appTheme.blueA400,
|
||||
),
|
||||
child: Text(
|
||||
'+',
|
||||
style: theme.textTheme.titleSmall,
|
||||
),
|
||||
onPressed: () => context.read<RecipeCreateCubit>().addStep(),
|
||||
),
|
||||
),
|
||||
SizedBox(height: 8),
|
||||
Container(
|
||||
height: 48,
|
||||
width: MediaQuery.of(context).size.width,
|
||||
child: ElevatedButton(
|
||||
style: ElevatedButton.styleFrom(
|
||||
elevation: 0,
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
),
|
||||
backgroundColor: appTheme.blueA400,
|
||||
),
|
||||
child: Text(
|
||||
'-',
|
||||
style: theme.textTheme.titleSmall,
|
||||
),
|
||||
onPressed: () => context.read<RecipeCreateCubit>().removeStep(),
|
||||
),
|
||||
),
|
||||
SizedBox(height: 24),
|
||||
Text('Ссылка на видео (video)'),
|
||||
SizedBox(height: 8),
|
||||
CustomTextFormField(
|
||||
hintText: 'Введите описание шага',
|
||||
fillColor: appTheme.gray50,
|
||||
controller: state.videoLinkController,
|
||||
),
|
||||
SizedBox(height: 24),
|
||||
Container(
|
||||
height: 48,
|
||||
width: MediaQuery.of(context).size.width,
|
||||
child: ElevatedButton(
|
||||
style: ElevatedButton.styleFrom(
|
||||
elevation: 0,
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
),
|
||||
backgroundColor: appTheme.blueA400,
|
||||
),
|
||||
child: Text(
|
||||
'Опубликовать',
|
||||
style: theme.textTheme.titleSmall,
|
||||
),
|
||||
onPressed: () => context.read<RecipeCreateCubit>().editRecipe(createRecipeFromState(state), recipeID),
|
||||
),
|
||||
),
|
||||
],
|
||||
),),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
_buildStep(BuildContext context, int stepNumber, RecipeCreateStateLoaded state) {
|
||||
return Column(
|
||||
children: [
|
||||
Row(children: [Text('Шаг ${stepNumber + 1}'), Spacer(), Text('Всего шагов: ${state.numberOfSteps}')]),
|
||||
SizedBox(height: 8),
|
||||
GestureDetector(
|
||||
onTap: () async {
|
||||
final pickedFile = await state.picker.pickImage(source: ImageSource.gallery);
|
||||
context.read<RecipeCreateCubit>().uploadStepImage(pickedFile, stepNumber);
|
||||
},
|
||||
child: state.stepImages[stepNumber] == null
|
||||
? Container(
|
||||
height: 128,
|
||||
decoration: AppDecoration.fillGray.copyWith(
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
Icon(
|
||||
Icons.photo_library,
|
||||
color: appTheme.blueGray300,
|
||||
),
|
||||
SizedBox(width: 8),
|
||||
Text(
|
||||
"Добавить фото",
|
||||
selectionColor: appTheme.blueA400,
|
||||
),
|
||||
SizedBox(height: 8),
|
||||
],
|
||||
),
|
||||
)
|
||||
: Image.network(
|
||||
baseUrl + '/storage/' + state.stepImages[stepNumber]!.path,
|
||||
height: 150,
|
||||
fit: BoxFit.cover,
|
||||
),),
|
||||
SizedBox(height: 16),
|
||||
CustomTextFormField(
|
||||
hintText: 'Введите описание шага',
|
||||
maxLines: 8,
|
||||
minLines: 8,
|
||||
fillColor: appTheme.gray50,
|
||||
controller: state.stepControllers[stepNumber],
|
||||
),
|
||||
SizedBox(height: 24),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
_buildIngredient(Ingredient ingredient) {
|
||||
return Container(
|
||||
margin: EdgeInsets.symmetric(horizontal: 12),
|
||||
padding: EdgeInsets.symmetric(
|
||||
horizontal: 8,
|
||||
vertical: 8,
|
||||
),
|
||||
decoration: AppDecoration.fillGray.copyWith(
|
||||
borderRadius: BorderRadiusStyle.roundedBorder5,
|
||||
),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.end,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Padding(
|
||||
padding: EdgeInsets.only(
|
||||
left: 8,
|
||||
top: 8,
|
||||
),
|
||||
child: Text(
|
||||
ingredient.name.length > 8 ? ingredient.name.substring(0, 8) : ingredient.name,
|
||||
style: CustomTextStyles.titleMedium16,
|
||||
),
|
||||
),
|
||||
Padding(
|
||||
padding: EdgeInsets.only(
|
||||
left: 24,
|
||||
top: 4,
|
||||
bottom: 4,
|
||||
),
|
||||
child: Text(
|
||||
ingredient.portion.toString() + ' ' + ingredient.unit.toString(),
|
||||
),
|
||||
),
|
||||
Spacer(),
|
||||
GestureDetector(
|
||||
// onTap: removeIngredient(context, ingredient),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.only(right: 10),
|
||||
child: Icon(
|
||||
Icons.close,
|
||||
color: appTheme.blueGray300,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
_buildOptions(List<String> _options) {
|
||||
return (TextEditingValue textEditingValue) {
|
||||
return _options.where((String option) {
|
||||
return true;
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
selectProduct(AutocompleteOnSelected<String> onSelected, String option, RecipeCreateStateLoaded state) {
|
||||
state.productNameController.text = option;
|
||||
return () {
|
||||
onSelected(option);
|
||||
};
|
||||
}
|
||||
|
||||
getAutocomplete(BuildContext context, FocusNode focusNode) {
|
||||
return (value) {
|
||||
if (value.length > 2) {
|
||||
context.read<RecipeCreateCubit>().inputProduct(value);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
changeType(BuildContext context) {
|
||||
return (value) {
|
||||
context.read<RecipeCreateCubit>().changeType(value);
|
||||
};
|
||||
}
|
||||
|
||||
changeStatus(BuildContext context) {
|
||||
return (value) {
|
||||
context.read<RecipeCreateCubit>().changeStatus(value);
|
||||
};
|
||||
}
|
||||
|
||||
changeCookingTime(BuildContext context) {
|
||||
return (value) {
|
||||
context.read<RecipeCreateCubit>().changeCookingTime(value);
|
||||
};
|
||||
}
|
||||
|
||||
RecipeModel createRecipeFromState(RecipeCreateStateLoaded state) {
|
||||
List<Ingredient> ingredients = state.ingredients;
|
||||
|
||||
List<step.Step> steps = [];
|
||||
for (int i = 0; i < state.stepControllers.length; i++) {
|
||||
if (state.stepControllers[i].text.isNotEmpty) {
|
||||
steps.add(step.Step(
|
||||
description: state.stepControllers[i].text,
|
||||
image: state.stepImages[i]?.path,
|
||||
),);
|
||||
}
|
||||
}
|
||||
|
||||
final RecipeModel recipe = RecipeModel(
|
||||
ingredients: ingredients,
|
||||
steps: steps,
|
||||
state: state.selectedStatus.value,
|
||||
type: state.selectedType.value,
|
||||
cooking_time: state.selectedCookingTime.value,
|
||||
description: state.stepDescriptionController.text,
|
||||
title: state.titleController.text,
|
||||
image: state.recipeImage?.path,
|
||||
video_url: state.videoLinkController.text.isNotEmpty ? state.videoLinkController.text : null,
|
||||
);
|
||||
return recipe;
|
||||
}
|
||||
}
|
||||
121
lib/features/recipes/presentation/widgets/image_widget.dart
Normal file
121
lib/features/recipes/presentation/widgets/image_widget.dart
Normal file
@@ -0,0 +1,121 @@
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:eatvio_app/features/recipes/presentation/bloc/create_recipe_cubit/create_recipe_cubit.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 'package:image_picker/image_picker.dart';
|
||||
|
||||
class ImageWidget extends StatefulWidget {
|
||||
const ImageWidget({super.key});
|
||||
|
||||
@override
|
||||
State<ImageWidget> createState() => _ImageWidgetState();
|
||||
}
|
||||
|
||||
class _ImageWidgetState extends State<ImageWidget> {
|
||||
XFile? image;
|
||||
late final CreateRecipeCubit createRecipeCubit;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
createRecipeCubit = context.read<CreateRecipeCubit>();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return GestureDetector(
|
||||
onTap: () {
|
||||
_showImagePicker();
|
||||
},
|
||||
child: image == null
|
||||
? SvgPicture.asset(
|
||||
width: MediaQuery.of(context).size.width - 32,
|
||||
'assets/icons/upload_photo_of_portion.svg',
|
||||
)
|
||||
: Stack(
|
||||
children: [
|
||||
ClipRRect(
|
||||
borderRadius: BorderRadius.circular(20),
|
||||
child: Image.file(
|
||||
File(image!.path),
|
||||
),
|
||||
),
|
||||
Positioned(
|
||||
right: 16,
|
||||
top: 16,
|
||||
child: GestureDetector(
|
||||
onTap: () {
|
||||
setState(() {
|
||||
image = null;
|
||||
});
|
||||
createRecipeCubit.replaceImage(null);
|
||||
},
|
||||
child: Container(
|
||||
padding: EdgeInsets.all(4),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white,
|
||||
shape: BoxShape.circle,
|
||||
),
|
||||
child: Icon(Icons.close_rounded),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
_showImagePicker() {
|
||||
showCupertinoModalPopup<void>(
|
||||
context: context,
|
||||
builder: (context) => CupertinoActionSheet(
|
||||
actions: [
|
||||
CupertinoActionSheetAction(
|
||||
onPressed: () async {
|
||||
final picker = ImagePicker();
|
||||
image = await picker.pickImage(source: ImageSource.camera);
|
||||
setState(() {});
|
||||
if (image != null) {
|
||||
createRecipeCubit.replaceImage(File(image!.path));
|
||||
}
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
child: const Row(
|
||||
children: const [
|
||||
Icon(Icons.photo_camera),
|
||||
SizedBox(width: 8),
|
||||
Text('Фото'),
|
||||
],
|
||||
),
|
||||
),
|
||||
CupertinoActionSheetAction(
|
||||
onPressed: () async {
|
||||
final picker = ImagePicker();
|
||||
image = await picker.pickImage(source: ImageSource.gallery);
|
||||
setState(() {});
|
||||
if (image != null) {
|
||||
createRecipeCubit.replaceImage(File(image!.path));
|
||||
}
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
child: const Row(
|
||||
children: const [
|
||||
Icon(Icons.photo),
|
||||
SizedBox(width: 8),
|
||||
Text('Галерея'),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
cancelButton: CupertinoActionSheetAction(
|
||||
onPressed: () => Navigator.of(context).pop(),
|
||||
isDestructiveAction: true,
|
||||
child: const Text('Отмена'),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,113 @@
|
||||
import 'package:eatvio_app/core/data/selectionPopupModel/selection_popup_model.dart';
|
||||
import 'package:eatvio_app/core/widgets/custom_drop_down.dart';
|
||||
import 'package:eatvio_app/core/widgets/custom_text_form_field.dart';
|
||||
import 'package:eatvio_app/features/recipes/entities/recipe_create_entity.dart';
|
||||
import 'package:eatvio_app/features/recipes/presentation/bloc/create_recipe_cubit/create_recipe_cubit.dart';
|
||||
import 'package:eatvio_app/features/recipes/presentation/widgets/image_widget.dart';
|
||||
import 'package:eatvio_app/features/recipes/presentation/widgets/portion_widget.dart';
|
||||
import 'package:eatvio_app/features/recipes/presentation/widgets/tags_widget.dart';
|
||||
import 'package:eatvio_app/features/recipes/presentation/widgets/time_of_cook_widget.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
|
||||
class InfoAboutRecipeWidget extends StatefulWidget {
|
||||
const InfoAboutRecipeWidget({super.key});
|
||||
|
||||
@override
|
||||
State<InfoAboutRecipeWidget> createState() => _InfoAboutRecipeWidgetState();
|
||||
}
|
||||
|
||||
class _InfoAboutRecipeWidgetState extends State<InfoAboutRecipeWidget> {
|
||||
final List<SelectionPopupModel> items = const [
|
||||
SelectionPopupModel(title: 'Первые Блюда'),
|
||||
SelectionPopupModel(title: 'Вторые Блюда'),
|
||||
SelectionPopupModel(title: 'Салаты'),
|
||||
SelectionPopupModel(title: 'Сладости'),
|
||||
SelectionPopupModel(title: 'Напитки'),
|
||||
SelectionPopupModel(title: 'Прочее'),
|
||||
];
|
||||
late SelectionPopupModel selectedType;
|
||||
late final CreateRecipeCubit createRecipeCubit;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
selectedType = items.first;
|
||||
createRecipeCubit = context.read<CreateRecipeCubit>();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Material(
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(16),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text('Название'),
|
||||
SizedBox(height: 4),
|
||||
CustomTextFormField(
|
||||
hintText: 'Например: тар-тар из говядины',
|
||||
onChanged: (value) {
|
||||
createRecipeCubit.replaceTitle(value);
|
||||
},
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
Text('Тип блюда'),
|
||||
SizedBox(height: 4),
|
||||
CustomDropDown(
|
||||
items: items,
|
||||
value: selectedType,
|
||||
onChanged: (newSelectedType) {
|
||||
setState(() {
|
||||
selectedType = newSelectedType;
|
||||
});
|
||||
createRecipeCubit.changeType(
|
||||
switch (selectedType.title) {
|
||||
'Первые Блюда' => TypeOfRecipe.first_meals,
|
||||
'Вторые Блюда' => TypeOfRecipe.second_meals,
|
||||
'Салаты' => TypeOfRecipe.salads,
|
||||
'Сладости' => TypeOfRecipe.sweets,
|
||||
'Напитки' => TypeOfRecipe.drinks,
|
||||
_ => TypeOfRecipe.other,
|
||||
},
|
||||
);
|
||||
},
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
TagsWidget(),
|
||||
const SizedBox(height: 12),
|
||||
PortionWidget(),
|
||||
const SizedBox(height: 12),
|
||||
TimeOfCookWidget(),
|
||||
const SizedBox(height: 12),
|
||||
Text('Описание'),
|
||||
SizedBox(height: 4),
|
||||
CustomTextFormField(
|
||||
hintText: 'Расскажите, почему вы выбрали именно этот рецепт? Опишите, каким будет готовое блюдо и в чём его достоинства.',
|
||||
minLines: 5,
|
||||
maxLines: 5,
|
||||
onChanged: (value) {
|
||||
createRecipeCubit.replaceDescription(value);
|
||||
},
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
Text('Фото обложка'),
|
||||
SizedBox(height: 4),
|
||||
ImageWidget(),
|
||||
const SizedBox(height: 12),
|
||||
Text('Ссылка на видео (если есть)'),
|
||||
SizedBox(height: 4),
|
||||
CustomTextFormField(
|
||||
hintText: 'Прикрепите ссылку на видео',
|
||||
onChanged: (value) {
|
||||
createRecipeCubit.replaceVideoUrl(value);
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,223 @@
|
||||
import 'package:eatvio_app/core/theme/theme_helper.dart';
|
||||
import 'package:eatvio_app/core/widgets/custom_text_form_field.dart';
|
||||
import 'package:eatvio_app/features/recipes/entities/recipe_create_entity.dart';
|
||||
import 'package:eatvio_app/features/recipes/presentation/bloc/create_recipe_cubit/create_recipe_cubit.dart';
|
||||
import 'package:eatvio_app/features/recipes/presentation/bloc/create_recipe_cubit/create_recipe_state.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
|
||||
class IngredientsWidget extends StatefulWidget {
|
||||
const IngredientsWidget({super.key});
|
||||
|
||||
@override
|
||||
State<IngredientsWidget> createState() => _IngredientsWidgetState();
|
||||
}
|
||||
|
||||
class _IngredientsWidgetState extends State<IngredientsWidget> {
|
||||
late final CreateRecipeCubit createRecipeCubit;
|
||||
final nameController = TextEditingController();
|
||||
final gramsController = TextEditingController();
|
||||
final pcsController = TextEditingController();
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
createRecipeCubit = context.read<CreateRecipeCubit>();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return BlocBuilder<CreateRecipeCubit, CreateRecipeState>(
|
||||
builder: (BuildContext context, CreateRecipeState state) {
|
||||
return Material(
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 16),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16),
|
||||
child: Text(
|
||||
'Ингредиенты ',
|
||||
style: TextStyle(
|
||||
fontSize: 20,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: Color(0xFF323F4B),
|
||||
),
|
||||
),
|
||||
),
|
||||
ReorderableListView(
|
||||
shrinkWrap: true,
|
||||
physics: const NeverScrollableScrollPhysics(),
|
||||
onReorder: (int oldIndex, int newIndex) {
|
||||
setState(() {
|
||||
if (oldIndex < newIndex) {
|
||||
newIndex -= 1;
|
||||
}
|
||||
});
|
||||
createRecipeCubit.replaceIngredientPosition(oldIndex, newIndex);
|
||||
},
|
||||
children: <Widget>[
|
||||
for (int index = 0; index < state.entity.ingredients.length; index += 1)
|
||||
ListTile(
|
||||
contentPadding: EdgeInsets.symmetric(horizontal: 16),
|
||||
key: Key('$index'),
|
||||
leading: Icon(
|
||||
Icons.drag_handle_rounded,
|
||||
color: Colors.grey.shade600,
|
||||
),
|
||||
title: Text(state.entity.ingredients[index].name),
|
||||
subtitle: Text('${state.entity.ingredients[index].unit} ${state.entity.ingredients[index].portion == 'gr' ? 'гр' : 'шт'}'),
|
||||
trailing: IconButton(
|
||||
onPressed: () {
|
||||
createRecipeCubit.removeIngredient(index);
|
||||
},
|
||||
icon: Icon(
|
||||
Icons.delete_outline_rounded,
|
||||
color: Colors.grey.shade600,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
ListTile(
|
||||
onTap: () {
|
||||
nameController.clear();
|
||||
gramsController.clear();
|
||||
pcsController.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()) setStateInner) {
|
||||
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),
|
||||
SizedBox(
|
||||
width: MediaQuery.of(context).size.width - 32,
|
||||
child: CustomTextFormField(
|
||||
autofocus: true,
|
||||
controller: nameController,
|
||||
hintText: 'Введите название продукта',
|
||||
onChanged: (query) {},
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
Row(
|
||||
children: [
|
||||
SizedBox(
|
||||
width: MediaQuery.of(context).size.width / 2 - 20,
|
||||
child: CustomTextFormField(
|
||||
enabled: pcsController.text.isEmpty ? true : false,
|
||||
keyboardType: TextInputType.number,
|
||||
controller: gramsController,
|
||||
hintText: pcsController.text.isEmpty ? '100' : null,
|
||||
suffixText: 'гр',
|
||||
onChanged: (value) {
|
||||
setStateInner(() {});
|
||||
},
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
SizedBox(
|
||||
width: MediaQuery.of(context).size.width / 2 - 20,
|
||||
child: CustomTextFormField(
|
||||
enabled: gramsController.text.isEmpty ? true : false,
|
||||
keyboardType: TextInputType.number,
|
||||
controller: pcsController,
|
||||
onChanged: (value) {
|
||||
setStateInner(() {});
|
||||
},
|
||||
hintText: gramsController.text.isEmpty ? '1' : null,
|
||||
suffixText: 'шт',
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
InkWell(
|
||||
onTap: () {
|
||||
if (nameController.text.isNotEmpty && (pcsController.text.isNotEmpty || gramsController.text.isNotEmpty)) {
|
||||
createRecipeCubit.addIngredient(
|
||||
IngredientEntity(
|
||||
name: nameController.text,
|
||||
unit: gramsController.text.isEmpty ? pcsController.text : gramsController.text,
|
||||
portion: gramsController.text.isEmpty ? 'pcs' : 'gr',
|
||||
),
|
||||
);
|
||||
Navigator.of(context).pop();
|
||||
setState(() {});
|
||||
}
|
||||
},
|
||||
borderRadius: BorderRadius.circular(20),
|
||||
child: Ink(
|
||||
width: MediaQuery.of(context).size.width - 32,
|
||||
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,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
),
|
||||
title: Text(
|
||||
'Добавить ингредиент',
|
||||
style: TextStyle(
|
||||
color: appTheme.blueA400,
|
||||
),
|
||||
),
|
||||
leading: Icon(
|
||||
Icons.add_rounded,
|
||||
color: appTheme.blueA400,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
import 'package:eatvio_app/core/widgets/custom_text_form_field.dart';
|
||||
import 'package:eatvio_app/features/recipes/presentation/bloc/create_recipe_cubit/create_recipe_cubit.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
|
||||
class PortionWidget extends StatefulWidget {
|
||||
const PortionWidget({super.key});
|
||||
|
||||
@override
|
||||
State<PortionWidget> createState() => _PortionWidgetState();
|
||||
}
|
||||
|
||||
class _PortionWidgetState extends State<PortionWidget> {
|
||||
late final CreateRecipeCubit createRecipeCubit;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
createRecipeCubit = context.read<CreateRecipeCubit>();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Expanded(
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text('Кол-во порций'),
|
||||
SizedBox(height: 4),
|
||||
CustomTextFormField(
|
||||
hintText: '1',
|
||||
onChanged: (value) {
|
||||
createRecipeCubit.replacePortionsCount(int.parse(value));
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
Expanded(
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text('Ккал в одной порции'),
|
||||
SizedBox(height: 4),
|
||||
CustomTextFormField(
|
||||
hintText: '100',
|
||||
suffixText: 'Ккал',
|
||||
onChanged: (value) {
|
||||
createRecipeCubit.replaceEnegryInPortion(double.parse(value));
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
126
lib/features/recipes/presentation/widgets/step_image_widget.dart
Normal file
126
lib/features/recipes/presentation/widgets/step_image_widget.dart
Normal file
@@ -0,0 +1,126 @@
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:eatvio_app/features/recipes/presentation/bloc/create_recipe_cubit/create_recipe_cubit.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 'package:image_picker/image_picker.dart';
|
||||
|
||||
class StepImageWidget extends StatefulWidget {
|
||||
final int index;
|
||||
|
||||
const StepImageWidget({
|
||||
super.key,
|
||||
required this.index,
|
||||
});
|
||||
|
||||
@override
|
||||
State<StepImageWidget> createState() => _StepImageWidgetState();
|
||||
}
|
||||
|
||||
class _StepImageWidgetState extends State<StepImageWidget> {
|
||||
XFile? image;
|
||||
late final CreateRecipeCubit createRecipeCubit;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
createRecipeCubit = context.read<CreateRecipeCubit>();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return GestureDetector(
|
||||
onTap: () {
|
||||
_showImagePicker();
|
||||
},
|
||||
child: image == null
|
||||
? SvgPicture.asset(
|
||||
width: MediaQuery.of(context).size.width - 32,
|
||||
'assets/icons/add_step_photo.svg',
|
||||
)
|
||||
: Stack(
|
||||
children: [
|
||||
ClipRRect(
|
||||
borderRadius: BorderRadius.circular(20),
|
||||
child: Image.file(
|
||||
File(image!.path),
|
||||
),
|
||||
),
|
||||
Positioned(
|
||||
right: 16,
|
||||
top: 16,
|
||||
child: GestureDetector(
|
||||
onTap: () {
|
||||
setState(() {
|
||||
image = null;
|
||||
});
|
||||
createRecipeCubit.replaceImageInStep(null, widget.index);
|
||||
},
|
||||
child: Container(
|
||||
padding: EdgeInsets.all(4),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white,
|
||||
shape: BoxShape.circle,
|
||||
),
|
||||
child: Icon(Icons.close_rounded),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
_showImagePicker() {
|
||||
showCupertinoModalPopup<void>(
|
||||
context: context,
|
||||
builder: (context) => CupertinoActionSheet(
|
||||
actions: [
|
||||
CupertinoActionSheetAction(
|
||||
onPressed: () async {
|
||||
final picker = ImagePicker();
|
||||
image = await picker.pickImage(source: ImageSource.camera);
|
||||
setState(() {});
|
||||
if (image != null) {
|
||||
createRecipeCubit.replaceImageInStep(File(image!.path), widget.index);
|
||||
}
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
child: const Row(
|
||||
children: const [
|
||||
Icon(Icons.photo_camera),
|
||||
SizedBox(width: 8),
|
||||
Text('Фото'),
|
||||
],
|
||||
),
|
||||
),
|
||||
CupertinoActionSheetAction(
|
||||
onPressed: () async {
|
||||
final picker = ImagePicker();
|
||||
image = await picker.pickImage(source: ImageSource.gallery);
|
||||
setState(() {});
|
||||
if (image != null) {
|
||||
createRecipeCubit.replaceImageInStep(File(image!.path), widget.index);
|
||||
}
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
child: const Row(
|
||||
children: const [
|
||||
Icon(Icons.photo),
|
||||
SizedBox(width: 8),
|
||||
Text('Галерея'),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
cancelButton: CupertinoActionSheetAction(
|
||||
onPressed: () => Navigator.of(context).pop(),
|
||||
isDestructiveAction: true,
|
||||
child: const Text('Отмена'),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
135
lib/features/recipes/presentation/widgets/steps_widget.dart
Normal file
135
lib/features/recipes/presentation/widgets/steps_widget.dart
Normal file
@@ -0,0 +1,135 @@
|
||||
import 'package:eatvio_app/core/theme/theme_helper.dart';
|
||||
import 'package:eatvio_app/core/widgets/custom_text_form_field.dart';
|
||||
import 'package:eatvio_app/features/recipes/presentation/bloc/create_recipe_cubit/create_recipe_cubit.dart';
|
||||
import 'package:eatvio_app/features/recipes/presentation/bloc/create_recipe_cubit/create_recipe_state.dart';
|
||||
import 'package:eatvio_app/features/recipes/presentation/widgets/step_image_widget.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
|
||||
class StepsWidget extends StatefulWidget {
|
||||
const StepsWidget({super.key});
|
||||
|
||||
@override
|
||||
State<StepsWidget> createState() => _StepsWidgetState();
|
||||
}
|
||||
|
||||
class _StepsWidgetState extends State<StepsWidget> {
|
||||
late final CreateRecipeCubit createRecipeCubit;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
createRecipeCubit = context.read<CreateRecipeCubit>();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return BlocBuilder<CreateRecipeCubit, CreateRecipeState>(
|
||||
builder: (BuildContext context, CreateRecipeState state) {
|
||||
return Material(
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(16),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
'Пошаговая инструкция',
|
||||
style: TextStyle(
|
||||
fontSize: 20,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: Color(0xFF323F4B),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
ListView.separated(
|
||||
shrinkWrap: true,
|
||||
physics: const NeverScrollableScrollPhysics(),
|
||||
itemBuilder: (context, index) => Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Container(
|
||||
padding: EdgeInsets.symmetric(
|
||||
horizontal: 12,
|
||||
vertical: 4,
|
||||
),
|
||||
decoration: BoxDecoration(
|
||||
color: appTheme.blueA400,
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
),
|
||||
child: Text(
|
||||
'Шаг ${index + 1}',
|
||||
style: TextStyle(
|
||||
color: Colors.white,
|
||||
fontWeight: FontWeight.w600,
|
||||
),
|
||||
),
|
||||
),
|
||||
GestureDetector(
|
||||
onTap: () {
|
||||
context.read<CreateRecipeCubit>().removeStep(state.entity.steps[index]);
|
||||
},
|
||||
child: Icon(Icons.delete_outline_rounded),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
Text('Описание'),
|
||||
SizedBox(height: 4),
|
||||
CustomTextFormField(
|
||||
hintText: 'Например: подготовьте ингредиенты',
|
||||
maxLines: 5,
|
||||
minLines: 3,
|
||||
onChanged: (value) {
|
||||
createRecipeCubit.replaceDescriptionInStep(value, index);
|
||||
},
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
Text('Фото шага'),
|
||||
SizedBox(height: 4),
|
||||
StepImageWidget(
|
||||
index: index,
|
||||
),
|
||||
],
|
||||
),
|
||||
itemCount: state.entity.steps.length,
|
||||
separatorBuilder: (BuildContext context, int index) => const SizedBox(height: 24),
|
||||
),
|
||||
InkWell(
|
||||
onTap: () {
|
||||
context.read<CreateRecipeCubit>().addStep();
|
||||
},
|
||||
borderRadius: BorderRadius.circular(20),
|
||||
child: Ink(
|
||||
width: MediaQuery.of(context).size.width - 32,
|
||||
decoration: BoxDecoration(
|
||||
color: appTheme.blueA400,
|
||||
borderRadius: BorderRadius.circular(20),
|
||||
),
|
||||
child: Container(
|
||||
padding: const EdgeInsets.all(16),
|
||||
alignment: Alignment.center,
|
||||
child: Text(
|
||||
'Добавить шаг',
|
||||
style: TextStyle(
|
||||
fontSize: 16,
|
||||
color: Colors.white,
|
||||
fontWeight: FontWeight.w600,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
82
lib/features/recipes/presentation/widgets/tags_widget.dart
Normal file
82
lib/features/recipes/presentation/widgets/tags_widget.dart
Normal file
@@ -0,0 +1,82 @@
|
||||
import 'package:eatvio_app/core/theme/theme_helper.dart';
|
||||
import 'package:eatvio_app/core/widgets/custom_text_form_field.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class TagsWidget extends StatefulWidget {
|
||||
const TagsWidget({super.key});
|
||||
|
||||
@override
|
||||
State<TagsWidget> createState() => _TagsWidgetState();
|
||||
}
|
||||
|
||||
class _TagsWidgetState extends State<TagsWidget> {
|
||||
final tagController = TextEditingController();
|
||||
final List<String> tags = [];
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text('Теги'),
|
||||
SizedBox(height: 4),
|
||||
CustomTextFormField(
|
||||
hintText: 'Например: без майонеза',
|
||||
controller: tagController,
|
||||
suffix: IconButton(
|
||||
onPressed: () {
|
||||
if (tagController.text.isNotEmpty) {
|
||||
setState(() {
|
||||
tags.add(tagController.text);
|
||||
tagController.clear();
|
||||
});
|
||||
}
|
||||
},
|
||||
icon: Icon(
|
||||
Icons.done,
|
||||
color: appTheme.blueA400,
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
Wrap(
|
||||
spacing: 8,
|
||||
runSpacing: 8,
|
||||
children: [
|
||||
for (int i = 0; i < tags.length; i++) ...[
|
||||
InkWell(
|
||||
onTap: () {
|
||||
setState(() {
|
||||
tags.remove(tags[i]);
|
||||
});
|
||||
},
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
child: Ink(
|
||||
padding: EdgeInsets.symmetric(vertical: 4, horizontal: 12),
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
color: Colors.grey.shade200,
|
||||
),
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Text(
|
||||
tags[i],
|
||||
),
|
||||
const SizedBox(width: 4),
|
||||
Icon(
|
||||
Icons.delete_outline_rounded,
|
||||
color: Colors.grey.shade600,
|
||||
size: 20,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
],
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,151 @@
|
||||
import 'package:eatvio_app/core/theme/theme_helper.dart';
|
||||
import 'package:eatvio_app/core/widgets/custom_text_form_field.dart';
|
||||
import 'package:eatvio_app/features/recipes/presentation/bloc/create_recipe_cubit/create_recipe_cubit.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
|
||||
class TimeOfCookWidget extends StatefulWidget {
|
||||
const TimeOfCookWidget({super.key});
|
||||
|
||||
@override
|
||||
State<TimeOfCookWidget> createState() => _TimeOfCookWidgetState();
|
||||
}
|
||||
|
||||
class _TimeOfCookWidgetState extends State<TimeOfCookWidget> {
|
||||
late final CreateRecipeCubit createRecipeCubit;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
createRecipeCubit = context.read<CreateRecipeCubit>();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
Text('Общее время приготовления'),
|
||||
const SizedBox(width: 8),
|
||||
GestureDetector(
|
||||
onTap: () {
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (context) => AlertDialog(
|
||||
content: Text(
|
||||
'Общее время приготовления - это период времени от начала приготовления блюда до подачи его на стол.',
|
||||
style: TextStyle(
|
||||
fontWeight: FontWeight.w500,
|
||||
color: Color(0xFF323F4B),
|
||||
height: 1.2,
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
child: Icon(
|
||||
Icons.help_outline_rounded,
|
||||
color: appTheme.blueA400,
|
||||
size: 20,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
SizedBox(height: 4),
|
||||
Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Expanded(
|
||||
child: CustomTextFormField(
|
||||
hintText: 'Часов',
|
||||
onChanged: (value) {
|
||||
if (createRecipeCubit.state.entity.totalCookTime == null) {
|
||||
createRecipeCubit.replaceTotalTime('${value.padLeft(2, '0')}:00');
|
||||
} else {
|
||||
createRecipeCubit.replaceTotalTime('${value.padLeft(2, '0')}:${createRecipeCubit.state.entity.totalCookTime!.substring(3, 5)}');
|
||||
}
|
||||
},
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
Expanded(
|
||||
child: CustomTextFormField(
|
||||
hintText: 'Минут',
|
||||
onChanged: (value) {
|
||||
if (createRecipeCubit.state.entity.totalCookTime == null) {
|
||||
createRecipeCubit.replaceTotalTime('00:${value.padLeft(2, '0')}');
|
||||
} else {
|
||||
createRecipeCubit.replaceTotalTime('${createRecipeCubit.state.entity.totalCookTime!.substring(0, 2)}:${value.padLeft(2, '0')}');
|
||||
}
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
Row(
|
||||
children: [
|
||||
Text('Активное время приготовления'),
|
||||
const SizedBox(width: 8),
|
||||
GestureDetector(
|
||||
onTap: () {
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (context) => AlertDialog(
|
||||
content: Text(
|
||||
'Активное время приготовления - это период времени, требующий активного участия в приготовлении блюда. Оно включает мытье и очистку продуктов, нарезку, шинковку, взбивание, перемешивание, размешивание, перекладывание и т.п.',
|
||||
style: TextStyle(
|
||||
fontWeight: FontWeight.w500,
|
||||
color: Color(0xFF323F4B),
|
||||
height: 1.2,
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
child: Icon(
|
||||
Icons.help_outline_rounded,
|
||||
color: appTheme.blueA400,
|
||||
size: 20,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
SizedBox(height: 4),
|
||||
Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Expanded(
|
||||
child: CustomTextFormField(
|
||||
hintText: 'Часов',
|
||||
onChanged: (value) {
|
||||
if (createRecipeCubit.state.entity.activeCookTime == null) {
|
||||
createRecipeCubit.replaceActiveTime('${value.padLeft(2, '0')}:00');
|
||||
} else {
|
||||
createRecipeCubit.replaceActiveTime('${value.padLeft(2, '0')}:${createRecipeCubit.state.entity.activeCookTime!.substring(3, 5)}');
|
||||
}
|
||||
},
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
Expanded(
|
||||
child: CustomTextFormField(
|
||||
hintText: 'Минут',
|
||||
onChanged: (value) {
|
||||
if (createRecipeCubit.state.entity.activeCookTime == null) {
|
||||
createRecipeCubit.replaceActiveTime('00:${value.padLeft(2, '0')}');
|
||||
} else {
|
||||
createRecipeCubit.replaceActiveTime('${createRecipeCubit.state.entity.activeCookTime!.substring(0, 2)}:${value.padLeft(2, '0')}');
|
||||
}
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -20,6 +20,7 @@ class UserRegistrationSheet with _$UserRegistrationSheet {
|
||||
required String lang,
|
||||
required int weight,
|
||||
required int height,
|
||||
required String phone,
|
||||
@JsonKey(name: 'chest_circumference') required int chestCircumference,
|
||||
@JsonKey(name: 'waist_circumference') required int waistCircumference,
|
||||
@JsonKey(name: 'hip_circumference') required int hipCircumference,
|
||||
|
||||
@@ -39,6 +39,7 @@ mixin _$UserRegistrationSheet {
|
||||
String get lang => throw _privateConstructorUsedError;
|
||||
int get weight => throw _privateConstructorUsedError;
|
||||
int get height => throw _privateConstructorUsedError;
|
||||
String get phone => throw _privateConstructorUsedError;
|
||||
@JsonKey(name: 'chest_circumference')
|
||||
int get chestCircumference => throw _privateConstructorUsedError;
|
||||
@JsonKey(name: 'waist_circumference')
|
||||
@@ -98,6 +99,7 @@ abstract class $UserRegistrationSheetCopyWith<$Res> {
|
||||
String lang,
|
||||
int weight,
|
||||
int height,
|
||||
String phone,
|
||||
@JsonKey(name: 'chest_circumference') int chestCircumference,
|
||||
@JsonKey(name: 'waist_circumference') int waistCircumference,
|
||||
@JsonKey(name: 'hip_circumference') int hipCircumference,
|
||||
@@ -143,6 +145,7 @@ class _$UserRegistrationSheetCopyWithImpl<$Res,
|
||||
Object? lang = null,
|
||||
Object? weight = null,
|
||||
Object? height = null,
|
||||
Object? phone = null,
|
||||
Object? chestCircumference = null,
|
||||
Object? waistCircumference = null,
|
||||
Object? hipCircumference = null,
|
||||
@@ -211,6 +214,10 @@ class _$UserRegistrationSheetCopyWithImpl<$Res,
|
||||
? _value.height
|
||||
: height // ignore: cast_nullable_to_non_nullable
|
||||
as int,
|
||||
phone: null == phone
|
||||
? _value.phone
|
||||
: phone // ignore: cast_nullable_to_non_nullable
|
||||
as String,
|
||||
chestCircumference: null == chestCircumference
|
||||
? _value.chestCircumference
|
||||
: chestCircumference // ignore: cast_nullable_to_non_nullable
|
||||
@@ -294,6 +301,7 @@ abstract class _$$UserRegistrationSheetImplCopyWith<$Res>
|
||||
String lang,
|
||||
int weight,
|
||||
int height,
|
||||
String phone,
|
||||
@JsonKey(name: 'chest_circumference') int chestCircumference,
|
||||
@JsonKey(name: 'waist_circumference') int waistCircumference,
|
||||
@JsonKey(name: 'hip_circumference') int hipCircumference,
|
||||
@@ -337,6 +345,7 @@ class __$$UserRegistrationSheetImplCopyWithImpl<$Res>
|
||||
Object? lang = null,
|
||||
Object? weight = null,
|
||||
Object? height = null,
|
||||
Object? phone = null,
|
||||
Object? chestCircumference = null,
|
||||
Object? waistCircumference = null,
|
||||
Object? hipCircumference = null,
|
||||
@@ -405,6 +414,10 @@ class __$$UserRegistrationSheetImplCopyWithImpl<$Res>
|
||||
? _value.height
|
||||
: height // ignore: cast_nullable_to_non_nullable
|
||||
as int,
|
||||
phone: null == phone
|
||||
? _value.phone
|
||||
: phone // ignore: cast_nullable_to_non_nullable
|
||||
as String,
|
||||
chestCircumference: null == chestCircumference
|
||||
? _value.chestCircumference
|
||||
: chestCircumference // ignore: cast_nullable_to_non_nullable
|
||||
@@ -483,6 +496,7 @@ class _$UserRegistrationSheetImpl implements _UserRegistrationSheet {
|
||||
required this.lang,
|
||||
required this.weight,
|
||||
required this.height,
|
||||
required this.phone,
|
||||
@JsonKey(name: 'chest_circumference') required this.chestCircumference,
|
||||
@JsonKey(name: 'waist_circumference') required this.waistCircumference,
|
||||
@JsonKey(name: 'hip_circumference') required this.hipCircumference,
|
||||
@@ -533,6 +547,8 @@ class _$UserRegistrationSheetImpl implements _UserRegistrationSheet {
|
||||
@override
|
||||
final int height;
|
||||
@override
|
||||
final String phone;
|
||||
@override
|
||||
@JsonKey(name: 'chest_circumference')
|
||||
final int chestCircumference;
|
||||
@override
|
||||
@@ -577,7 +593,7 @@ class _$UserRegistrationSheetImpl implements _UserRegistrationSheet {
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'UserRegistrationSheet(firstName: $firstName, lastName: $lastName, email: $email, password: $password, passwordConfirmation: $passwordConfirmation, termsAccepted: $termsAccepted, birthdate: $birthdate, gender: $gender, location: $location, responseBody: $responseBody, lang: $lang, weight: $weight, height: $height, chestCircumference: $chestCircumference, waistCircumference: $waistCircumference, hipCircumference: $hipCircumference, bloodPressureUp: $bloodPressureUp, bloodPressureDown: $bloodPressureDown, hasHypo: $hasHypo, hasHyper: $hasHyper, aboutAllergy: $aboutAllergy, aboutTakingPils: $aboutTakingPils, aboutSportHobbies: $aboutSportHobbies, favoriteFoods: $favoriteFoods, unfavoriteFoods: $unfavoriteFoods, additionalInfo: $additionalInfo, activityCoefficient: $activityCoefficient)';
|
||||
return 'UserRegistrationSheet(firstName: $firstName, lastName: $lastName, email: $email, password: $password, passwordConfirmation: $passwordConfirmation, termsAccepted: $termsAccepted, birthdate: $birthdate, gender: $gender, location: $location, responseBody: $responseBody, lang: $lang, weight: $weight, height: $height, phone: $phone, chestCircumference: $chestCircumference, waistCircumference: $waistCircumference, hipCircumference: $hipCircumference, bloodPressureUp: $bloodPressureUp, bloodPressureDown: $bloodPressureDown, hasHypo: $hasHypo, hasHyper: $hasHyper, aboutAllergy: $aboutAllergy, aboutTakingPils: $aboutTakingPils, aboutSportHobbies: $aboutSportHobbies, favoriteFoods: $favoriteFoods, unfavoriteFoods: $unfavoriteFoods, additionalInfo: $additionalInfo, activityCoefficient: $activityCoefficient)';
|
||||
}
|
||||
|
||||
@override
|
||||
@@ -606,6 +622,7 @@ class _$UserRegistrationSheetImpl implements _UserRegistrationSheet {
|
||||
(identical(other.lang, lang) || other.lang == lang) &&
|
||||
(identical(other.weight, weight) || other.weight == weight) &&
|
||||
(identical(other.height, height) || other.height == height) &&
|
||||
(identical(other.phone, phone) || other.phone == phone) &&
|
||||
(identical(other.chestCircumference, chestCircumference) ||
|
||||
other.chestCircumference == chestCircumference) &&
|
||||
(identical(other.waistCircumference, waistCircumference) ||
|
||||
@@ -652,6 +669,7 @@ class _$UserRegistrationSheetImpl implements _UserRegistrationSheet {
|
||||
lang,
|
||||
weight,
|
||||
height,
|
||||
phone,
|
||||
chestCircumference,
|
||||
waistCircumference,
|
||||
hipCircumference,
|
||||
@@ -701,6 +719,7 @@ abstract class _UserRegistrationSheet implements UserRegistrationSheet {
|
||||
required final String lang,
|
||||
required final int weight,
|
||||
required final int height,
|
||||
required final String phone,
|
||||
@JsonKey(name: 'chest_circumference')
|
||||
required final int chestCircumference,
|
||||
@JsonKey(name: 'waist_circumference')
|
||||
@@ -756,6 +775,8 @@ abstract class _UserRegistrationSheet implements UserRegistrationSheet {
|
||||
@override
|
||||
int get height;
|
||||
@override
|
||||
String get phone;
|
||||
@override
|
||||
@JsonKey(name: 'chest_circumference')
|
||||
int get chestCircumference;
|
||||
@override
|
||||
|
||||
@@ -22,6 +22,7 @@ _$UserRegistrationSheetImpl _$$UserRegistrationSheetImplFromJson(
|
||||
lang: json['lang'] as String,
|
||||
weight: (json['weight'] as num).toInt(),
|
||||
height: (json['height'] as num).toInt(),
|
||||
phone: json['phone'] as String,
|
||||
chestCircumference: (json['chest_circumference'] as num).toInt(),
|
||||
waistCircumference: (json['waist_circumference'] as num).toInt(),
|
||||
hipCircumference: (json['hip_circumference'] as num).toInt(),
|
||||
@@ -55,6 +56,7 @@ Map<String, dynamic> _$$UserRegistrationSheetImplToJson(
|
||||
'lang': instance.lang,
|
||||
'weight': instance.weight,
|
||||
'height': instance.height,
|
||||
'phone': instance.phone,
|
||||
'chest_circumference': instance.chestCircumference,
|
||||
'waist_circumference': instance.waistCircumference,
|
||||
'hip_circumference': instance.hipCircumference,
|
||||
|
||||
@@ -24,7 +24,7 @@ class RegistrationCubit extends Cubit<RegistrationState> {
|
||||
gender: '',
|
||||
location: '',
|
||||
responseBody: '',
|
||||
lang: '',
|
||||
lang: 'Русский',
|
||||
weight: 0,
|
||||
height: 0,
|
||||
chestCircumference: 0,
|
||||
@@ -40,6 +40,7 @@ class RegistrationCubit extends Cubit<RegistrationState> {
|
||||
favoriteFoods: '',
|
||||
unfavoriteFoods: '',
|
||||
additionalInfo: '',
|
||||
phone: '',
|
||||
),
|
||||
),
|
||||
);
|
||||
@@ -48,7 +49,11 @@ class RegistrationCubit extends Cubit<RegistrationState> {
|
||||
|
||||
Future<void> tryRegistration() async {
|
||||
try {
|
||||
final token = await registrationRepository.tryRegister(state.sheet);
|
||||
final token = await registrationRepository.tryRegister(
|
||||
state.sheet.copyWith(
|
||||
phone: state.sheet.phone.replaceAll(' ', '').replaceAll('(', '').replaceAll(')', ''),
|
||||
),
|
||||
);
|
||||
await TokenStorage.saveTokenInStorage(token);
|
||||
AppRouter.navigatorKey.currentContext?.go('/my-profile-screen');
|
||||
} catch (e) {
|
||||
|
||||
@@ -33,7 +33,7 @@ class _AdditionalInformationScreenState extends State<AdditionalInformationScree
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
'Дополнительная информация',
|
||||
'Дополнительная информация (не обязательно)',
|
||||
maxLines: 2,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
textAlign: TextAlign.start,
|
||||
|
||||
@@ -33,7 +33,7 @@ class _AllergiesScreenState extends State<AllergiesScreen> {
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(s.allergies_presence),
|
||||
Text(s.allergies_presence + ' (не обязательно)'),
|
||||
SizedBox(height: 4),
|
||||
CustomTextFormField(
|
||||
controller: enterInformationController,
|
||||
|
||||
@@ -58,7 +58,7 @@ class _BloodPressureScreenState extends State<BloodPressureScreen> {
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(s.up_pressure),
|
||||
Text(s.up_pressure + ' (не обязательно)'),
|
||||
SizedBox(height: 4),
|
||||
CustomTextFormField(
|
||||
inputFormatters: [digitsOnly],
|
||||
@@ -72,7 +72,7 @@ class _BloodPressureScreenState extends State<BloodPressureScreen> {
|
||||
},
|
||||
),
|
||||
SizedBox(height: 16),
|
||||
Text(s.down_pressure),
|
||||
Text(s.down_pressure + ' (не обязательно)'),
|
||||
SizedBox(height: 4),
|
||||
CustomTextFormField(
|
||||
inputFormatters: [digitsOnly],
|
||||
@@ -84,7 +84,7 @@ class _BloodPressureScreenState extends State<BloodPressureScreen> {
|
||||
},
|
||||
),
|
||||
SizedBox(height: 16),
|
||||
Text(s.do_you_have_hypotension_tendency),
|
||||
Text(s.do_you_have_hypotension_tendency + ' (не обязательно)'),
|
||||
SizedBox(height: 4),
|
||||
CustomDropDown(
|
||||
icon: Container(
|
||||
@@ -110,7 +110,7 @@ class _BloodPressureScreenState extends State<BloodPressureScreen> {
|
||||
},
|
||||
),
|
||||
SizedBox(height: 16),
|
||||
Text(s.do_you_have_hypertension_tendency),
|
||||
Text(s.do_you_have_hypertension_tendency + ' (не обязательно)'),
|
||||
SizedBox(height: 4),
|
||||
CustomDropDown(
|
||||
icon: Container(
|
||||
|
||||
@@ -39,7 +39,7 @@ class _BodyCircumferencesScreenState extends State<BodyCircumferencesScreen> {
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(s.chest_circumference),
|
||||
Text(s.chest_circumference + ' (не обязательно)'),
|
||||
SizedBox(height: 4),
|
||||
CustomTextFormField(
|
||||
inputFormatters: [digitsOnly],
|
||||
@@ -53,7 +53,7 @@ class _BodyCircumferencesScreenState extends State<BodyCircumferencesScreen> {
|
||||
},
|
||||
),
|
||||
SizedBox(height: 16),
|
||||
Text(s.waist_circumference),
|
||||
Text(s.waist_circumference + ' (не обязательно)'),
|
||||
SizedBox(height: 4),
|
||||
CustomTextFormField(
|
||||
inputFormatters: [digitsOnly],
|
||||
@@ -65,7 +65,7 @@ class _BodyCircumferencesScreenState extends State<BodyCircumferencesScreen> {
|
||||
},
|
||||
),
|
||||
SizedBox(height: 16),
|
||||
Text(s.hip_circumference),
|
||||
Text(s.hip_circumference + ' (не обязательно)'),
|
||||
SizedBox(height: 4),
|
||||
CustomTextFormField(
|
||||
inputFormatters: [digitsOnly],
|
||||
|
||||
@@ -3,7 +3,6 @@ import 'package:eatvio_app/features/registration/models/user_registration_model.
|
||||
import 'package:eatvio_app/features/registration/presentation/bloc/registration_cubit/registration_cubit.dart';
|
||||
import 'package:eatvio_app/core/theme/theme_helper.dart';
|
||||
import 'package:eatvio_app/core/widgets/custom_text_form_field.dart';
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:flutter_typeahead/flutter_typeahead.dart';
|
||||
@@ -34,6 +33,22 @@ class _CityOfLivingScreenState extends State<CityOfLivingScreen> {
|
||||
if (widget.sheet.location.isNotEmpty) {
|
||||
cityController.text = widget.sheet.location;
|
||||
}
|
||||
tryGetLocation();
|
||||
}
|
||||
|
||||
tryGetLocation() async {
|
||||
final LocationData? locationData = await getLocationData();
|
||||
if (locationData != null) {
|
||||
final List<String> locations = await context
|
||||
.read<RegistrationCubit>()
|
||||
.getLocationsByCoordinates(locationData.latitude.toString(), locationData.longitude.toString());
|
||||
final registrationCubit = context.read<RegistrationCubit>();
|
||||
registrationCubit.updateSheet(registrationCubit.state.sheet.copyWith(location: locations.first));
|
||||
setState(() {
|
||||
cityController.text = locations.first;
|
||||
suggestionsController.suggestions = locations;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
Future<LocationData?> getLocationData() async {
|
||||
@@ -76,19 +91,21 @@ class _CityOfLivingScreenState extends State<CityOfLivingScreen> {
|
||||
Row(
|
||||
children: [
|
||||
SizedBox(
|
||||
width: MediaQuery.of(context).size.width - 80,
|
||||
width: MediaQuery.of(context).size.width - 32,
|
||||
child: TypeAheadField(
|
||||
debounceDuration: Duration(milliseconds: 300),
|
||||
controller: cityController,
|
||||
suggestionsController: suggestionsController,
|
||||
builder: (context, controller, focus) => CustomTextFormField(
|
||||
controller: cityController,
|
||||
focusNode: focus,
|
||||
autofocus: true,
|
||||
textStyle: TextStyle(
|
||||
fontSize: 16,
|
||||
color: Colors.black,
|
||||
),),
|
||||
controller: cityController,
|
||||
focusNode: focus,
|
||||
autofocus: true,
|
||||
hintText: 'Москва',
|
||||
textStyle: TextStyle(
|
||||
fontSize: 16,
|
||||
color: Colors.black,
|
||||
),
|
||||
),
|
||||
itemBuilder: (BuildContext context, String item) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.all(8.0),
|
||||
@@ -117,46 +134,6 @@ class _CityOfLivingScreenState extends State<CityOfLivingScreen> {
|
||||
},
|
||||
),
|
||||
),
|
||||
SizedBox(width: 8),
|
||||
InkWell(
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
onTap: !isLoading
|
||||
? () async {
|
||||
setState(() {
|
||||
isLoading = true;
|
||||
});
|
||||
final LocationData? locationData = await getLocationData();
|
||||
if (locationData != null) {
|
||||
final List<String> locations = await context
|
||||
.read<RegistrationCubit>()
|
||||
.getLocationsByCoordinates(locationData.latitude.toString(), locationData.longitude.toString());
|
||||
final registrationCubit = context.read<RegistrationCubit>();
|
||||
registrationCubit.updateSheet(registrationCubit.state.sheet.copyWith(location: locations.first));
|
||||
setState(() {
|
||||
cityController.text = locations.first;
|
||||
suggestionsController.suggestions = locations;
|
||||
});
|
||||
}
|
||||
setState(() {
|
||||
isLoading = false;
|
||||
});
|
||||
}
|
||||
: null,
|
||||
child: Ink(
|
||||
height: 40,
|
||||
width: 40,
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
color: Color(0xFFEAF2FD),
|
||||
),
|
||||
child: !isLoading
|
||||
? Icon(
|
||||
Icons.near_me_rounded,
|
||||
color: appTheme.blueA400,
|
||||
)
|
||||
: CupertinoActivityIndicator(),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
|
||||
@@ -20,7 +20,7 @@ class _CredentialsScreenState extends State<CredentialsScreen> {
|
||||
final passwordController = TextEditingController();
|
||||
|
||||
final passwordConfirmationController = TextEditingController();
|
||||
|
||||
final emailFocusNode = FocusNode();
|
||||
bool hidePassword = true;
|
||||
|
||||
@override
|
||||
@@ -29,6 +29,7 @@ class _CredentialsScreenState extends State<CredentialsScreen> {
|
||||
emailController.text = widget.sheet.email;
|
||||
passwordController.text = widget.sheet.password;
|
||||
passwordConfirmationController.text = widget.sheet.passwordConfirmation;
|
||||
emailFocusNode.requestFocus();
|
||||
}
|
||||
|
||||
@override
|
||||
@@ -44,9 +45,11 @@ class _CredentialsScreenState extends State<CredentialsScreen> {
|
||||
SizedBox(height: 4),
|
||||
CustomTextFormField(
|
||||
controller: emailController,
|
||||
focusNode: emailFocusNode,
|
||||
textInputType: TextInputType.emailAddress,
|
||||
keyboardType: TextInputType.emailAddress,
|
||||
hintText: 'Введите email',
|
||||
autofocus: true,
|
||||
textInputAction: TextInputAction.next,
|
||||
onChanged: (newValue) {
|
||||
final registrationCubit = context.read<RegistrationCubit>();
|
||||
|
||||
@@ -85,7 +85,7 @@ class _DateOfBirthScreenState extends State<DateOfBirthScreen> {
|
||||
showCupertinoModalPopup<void>(
|
||||
context: context,
|
||||
builder: (BuildContext context) => Container(
|
||||
height: 216,
|
||||
height: 314,
|
||||
padding: const EdgeInsets.only(top: 6),
|
||||
margin: EdgeInsets.only(
|
||||
bottom: MediaQuery.of(context).viewInsets.bottom,
|
||||
@@ -93,7 +93,41 @@ class _DateOfBirthScreenState extends State<DateOfBirthScreen> {
|
||||
color: CupertinoColors.systemBackground.resolveFrom(context),
|
||||
child: SafeArea(
|
||||
top: false,
|
||||
child: child,
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
SizedBox(height: 216, child: child,),
|
||||
const SizedBox(height: 16),
|
||||
Material(
|
||||
child: SizedBox(
|
||||
height: 52,
|
||||
child: InkWell(
|
||||
onTap: () {
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
child: Ink(
|
||||
width: MediaQuery.of(context).size.width - 32,
|
||||
height: 52,
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(20),
|
||||
color: appTheme.blueA400,
|
||||
),
|
||||
child: Center(
|
||||
child: Text(
|
||||
'Сохранить',
|
||||
style: TextStyle(
|
||||
color: Colors.white,
|
||||
fontSize: 18,
|
||||
fontWeight: FontWeight.w600,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
@@ -35,7 +35,7 @@ class _FavoriteFoodsScreenState extends State<FavoriteFoodsScreen> {
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(s.favorite_foods),
|
||||
Text(s.favorite_foods + ' (не обязательно)'),
|
||||
SizedBox(height: 4),
|
||||
CustomTextFormField(
|
||||
controller: favoriteFoodsController,
|
||||
@@ -50,7 +50,7 @@ class _FavoriteFoodsScreenState extends State<FavoriteFoodsScreen> {
|
||||
},
|
||||
),
|
||||
SizedBox(height: 16),
|
||||
Text(s.unfavorite_foods),
|
||||
Text(s.unfavorite_foods + ' (не обязательно)'),
|
||||
SizedBox(height: 4),
|
||||
CustomTextFormField(
|
||||
controller: unFavoriteFoodsController,
|
||||
|
||||
@@ -33,7 +33,7 @@ class _FavoriteSportsScreenState extends State<FavoriteSportsScreen> {
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(s.your_favorite_sports),
|
||||
Text(s.your_favorite_sports + ' (не обязательно)'),
|
||||
SizedBox(height: 4),
|
||||
CustomTextFormField(
|
||||
controller: enterInformationController,
|
||||
|
||||
@@ -33,7 +33,7 @@ class _MedicineIntakeScreenState extends State<MedicineIntakeScreen> {
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(s.do_you_take_pills),
|
||||
Text(s.do_you_take_pills + ' (не обязательно)'),
|
||||
SizedBox(height: 4),
|
||||
CustomTextFormField(
|
||||
controller: enterInformationController,
|
||||
|
||||
@@ -0,0 +1,66 @@
|
||||
import 'package:eatvio_app/core/widgets/custom_text_form_field.dart';
|
||||
import 'package:eatvio_app/features/registration/models/user_registration_model.dart';
|
||||
import 'package:eatvio_app/features/registration/presentation/bloc/registration_cubit/registration_cubit.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:mask_text_input_formatter/mask_text_input_formatter.dart';
|
||||
|
||||
class PhoneScreen extends StatefulWidget {
|
||||
final UserRegistrationSheet sheet;
|
||||
|
||||
const PhoneScreen({
|
||||
super.key,
|
||||
required this.sheet,
|
||||
});
|
||||
|
||||
@override
|
||||
State<PhoneScreen> createState() => _PhoneScreenState();
|
||||
}
|
||||
|
||||
class _PhoneScreenState extends State<PhoneScreen> {
|
||||
final phoneController = TextEditingController();
|
||||
final phoneFocus = FocusNode();
|
||||
|
||||
final maskFormatter = MaskTextInputFormatter(
|
||||
mask: '+7 (###) ### ## ##',
|
||||
filter: {"#": RegExp(r'[0-9]')},
|
||||
);
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
if (widget.sheet.phone.isNotEmpty) phoneController.text = widget.sheet.phone;
|
||||
phoneFocus.requestFocus();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return SingleChildScrollView(
|
||||
padding: EdgeInsets.only(bottom: 80, left: 16, right: 16, top: 16),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text('Номер телефона'),
|
||||
SizedBox(height: 4),
|
||||
CustomTextFormField(
|
||||
textInputAction: TextInputAction.done,
|
||||
autofocus: true,
|
||||
focusNode: phoneFocus,
|
||||
hintText: '+7 (999) 999 99 99',
|
||||
controller: phoneController,
|
||||
keyboardType: TextInputType.number,
|
||||
inputFormatters: [maskFormatter],
|
||||
onChanged: (newValue) {
|
||||
final registrationCubit = context.read<RegistrationCubit>();
|
||||
registrationCubit.updateSheet(
|
||||
registrationCubit.state.sheet.copyWith(
|
||||
phone: newValue,
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -13,9 +13,9 @@ import 'package:eatvio_app/features/registration/presentation/pages/favorite_foo
|
||||
import 'package:eatvio_app/features/registration/presentation/pages/favorite_sports_screen.dart';
|
||||
import 'package:eatvio_app/features/registration/presentation/pages/gender_screen.dart';
|
||||
import 'package:eatvio_app/features/registration/presentation/pages/height_screen.dart';
|
||||
import 'package:eatvio_app/features/registration/presentation/pages/language_select_screen.dart';
|
||||
import 'package:eatvio_app/features/registration/presentation/pages/medicine_intake_screen.dart';
|
||||
import 'package:eatvio_app/features/registration/presentation/pages/name_screen.dart';
|
||||
import 'package:eatvio_app/features/registration/presentation/pages/phone_screen.dart';
|
||||
import 'package:eatvio_app/features/registration/presentation/pages/weight_screen.dart';
|
||||
import 'package:eatvio_app/generated/l10n.dart';
|
||||
import 'package:eatvio_app/core/locator_service.dart';
|
||||
@@ -159,7 +159,8 @@ class _RegistrationScreenState extends State<RegistrationScreen> {
|
||||
});
|
||||
},
|
||||
children: [
|
||||
LanguageSelectScreen(sheet: state.sheet),
|
||||
// LanguageSelectScreen(sheet: state.sheet),
|
||||
PhoneScreen(sheet: state.sheet),
|
||||
CredentialsScreen(sheet: state.sheet),
|
||||
NameScreen(sheet: state.sheet),
|
||||
GenderScreen(sheet: state.sheet),
|
||||
@@ -184,6 +185,25 @@ class _RegistrationScreenState extends State<RegistrationScreen> {
|
||||
|
||||
Future<bool> canGoNext(RegistrationState state) async {
|
||||
switch (step) {
|
||||
case 1:
|
||||
if (state.sheet.phone.length != 18) {
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (context) => AlertDialog(
|
||||
title: Text('Ошибка'),
|
||||
content: Text('Введите корректный номер телефона'),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: () => Navigator.pop(context),
|
||||
child: Text('OK'),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
return false;
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
case 2:
|
||||
List<String> errors = validateCredentialsFields(
|
||||
state.sheet.email,
|
||||
|
||||
@@ -17,16 +17,20 @@ import 'package:intl/message_lookup_by_library.dart';
|
||||
import 'package:intl/src/intl_helpers.dart';
|
||||
|
||||
import 'messages_en.dart' as messages_en;
|
||||
import 'messages_ru.dart' as messages_ru;
|
||||
|
||||
typedef Future<dynamic> LibraryLoader();
|
||||
Map<String, LibraryLoader> _deferredLibraries = {
|
||||
'en': () => new SynchronousFuture(null),
|
||||
'ru': () => new SynchronousFuture(null),
|
||||
};
|
||||
|
||||
MessageLookupByLibrary? _findExact(String localeName) {
|
||||
switch (localeName) {
|
||||
case 'en':
|
||||
return messages_en.messages;
|
||||
case 'ru':
|
||||
return messages_ru.messages;
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
|
||||
306
lib/generated/intl/messages_ru.dart
Normal file
306
lib/generated/intl/messages_ru.dart
Normal file
@@ -0,0 +1,306 @@
|
||||
// DO NOT EDIT. This is code generated via package:intl/generate_localized.dart
|
||||
// This is a library that provides messages for a ru locale. All the
|
||||
// messages from the main program should be duplicated here with the same
|
||||
// function name.
|
||||
|
||||
// Ignore issues from commonly used lints in this file.
|
||||
// ignore_for_file:unnecessary_brace_in_string_interps, unnecessary_new
|
||||
// ignore_for_file:prefer_single_quotes,comment_references, directives_ordering
|
||||
// ignore_for_file:annotate_overrides,prefer_generic_function_type_aliases
|
||||
// ignore_for_file:unused_import, file_names, avoid_escaping_inner_quotes
|
||||
// ignore_for_file:unnecessary_string_interpolations, unnecessary_string_escapes
|
||||
|
||||
import 'package:intl/intl.dart';
|
||||
import 'package:intl/message_lookup_by_library.dart';
|
||||
|
||||
final messages = new MessageLookup();
|
||||
|
||||
typedef String MessageIfAbsent(String messageStr, List<dynamic> args);
|
||||
|
||||
class MessageLookup extends MessageLookupByLibrary {
|
||||
String get localeName => 'ru';
|
||||
|
||||
final messages = _notInlinedMessages(_notInlinedMessages);
|
||||
static Map<String, Function> _notInlinedMessages(_) => <String, Function>{
|
||||
"Moscow": MessageLookupByLibrary.simpleMessage("Москва"),
|
||||
"Russia": MessageLookupByLibrary.simpleMessage("Россия"),
|
||||
"about_diet": MessageLookupByLibrary.simpleMessage("Об этой диете"),
|
||||
"about_plan": MessageLookupByLibrary.simpleMessage("Об этом плане"),
|
||||
"active": MessageLookupByLibrary.simpleMessage("Активен"),
|
||||
"active_diet_state": MessageLookupByLibrary.simpleMessage("Активная"),
|
||||
"add": MessageLookupByLibrary.simpleMessage("Добавить"),
|
||||
"add_cover": MessageLookupByLibrary.simpleMessage("Добавьте обложку"),
|
||||
"add_measure": MessageLookupByLibrary.simpleMessage("Добавить замер"),
|
||||
"adding_plan": MessageLookupByLibrary.simpleMessage("Добавление плана"),
|
||||
"additional_information":
|
||||
MessageLookupByLibrary.simpleMessage("Дополнительная информация"),
|
||||
"admin": MessageLookupByLibrary.simpleMessage("Админ"),
|
||||
"all": MessageLookupByLibrary.simpleMessage("Все"),
|
||||
"allergies_presence":
|
||||
MessageLookupByLibrary.simpleMessage("Наличие аллергии"),
|
||||
"another_form_of_comment":
|
||||
MessageLookupByLibrary.simpleMessage("комментария"),
|
||||
"another_form_of_day": MessageLookupByLibrary.simpleMessage("дня"),
|
||||
"answer": MessageLookupByLibrary.simpleMessage("Ответить"),
|
||||
"archived": MessageLookupByLibrary.simpleMessage("В архиве"),
|
||||
"authentificate": MessageLookupByLibrary.simpleMessage("Войти"),
|
||||
"average_pressure": MessageLookupByLibrary.simpleMessage(
|
||||
"Средние показатели давления:",),
|
||||
"bodymass_chart": MessageLookupByLibrary.simpleMessage("График ИМТ"),
|
||||
"by_default": MessageLookupByLibrary.simpleMessage("По умолчянию"),
|
||||
"calories": MessageLookupByLibrary.simpleMessage("Калории"),
|
||||
"carb_type": MessageLookupByLibrary.simpleMessage("Мучное"),
|
||||
"carb_type_desc": MessageLookupByLibrary.simpleMessage(
|
||||
"Если вы любите мучные продукты, но хотите придерживаться диеты, вам подходит диета с добавленим мучных блюд на основе цельнозерновых вариантов. В них больше сложных углеводов и клетчатки, что способствует длительному чувству сытости и поддерживает здоровье пищеварительной системы.",),
|
||||
"carbohydrates": MessageLookupByLibrary.simpleMessage("Углеводы"),
|
||||
"chat": MessageLookupByLibrary.simpleMessage("Чат"),
|
||||
"chest": MessageLookupByLibrary.simpleMessage("Грудь"),
|
||||
"chest_circumference":
|
||||
MessageLookupByLibrary.simpleMessage("Обхват груди"),
|
||||
"choose_diet_duration":
|
||||
MessageLookupByLibrary.simpleMessage("Выберите срок диеты"),
|
||||
"choose_plan_duration":
|
||||
MessageLookupByLibrary.simpleMessage("Выберите срок плана"),
|
||||
"choose_start_diet_date": MessageLookupByLibrary.simpleMessage(
|
||||
"Выберите дату старты диеты. Она отобразиться у Вас в дневнике",),
|
||||
"circumference_empty": MessageLookupByLibrary.simpleMessage(
|
||||
"Все замеры обязательны к заполнению",),
|
||||
"circumference_is_not_a_number": MessageLookupByLibrary.simpleMessage(
|
||||
"Все замеры должны быть числами",),
|
||||
"circumferences_chart":
|
||||
MessageLookupByLibrary.simpleMessage("График обхватов"),
|
||||
"city": MessageLookupByLibrary.simpleMessage("Город"),
|
||||
"city_of_living":
|
||||
MessageLookupByLibrary.simpleMessage("Город проживания"),
|
||||
"comfort": MessageLookupByLibrary.simpleMessage("Комфорт"),
|
||||
"comment": MessageLookupByLibrary.simpleMessage("комментарий"),
|
||||
"comments": MessageLookupByLibrary.simpleMessage("Комментарии"),
|
||||
"comments_plural": MessageLookupByLibrary.simpleMessage("комментариев"),
|
||||
"complain": MessageLookupByLibrary.simpleMessage("Пожаловаться"),
|
||||
"continueButton": MessageLookupByLibrary.simpleMessage("Продолжить"),
|
||||
"continue_": MessageLookupByLibrary.simpleMessage("Продолжить"),
|
||||
"copied": MessageLookupByLibrary.simpleMessage("cкопирован"),
|
||||
"copy_link": MessageLookupByLibrary.simpleMessage("Скопировать ссылку"),
|
||||
"country": MessageLookupByLibrary.simpleMessage("Страна"),
|
||||
"create_diet": MessageLookupByLibrary.simpleMessage("Создание диеты"),
|
||||
"customer_price":
|
||||
MessageLookupByLibrary.simpleMessage("Цена для покупателя"),
|
||||
"date_of_birth": MessageLookupByLibrary.simpleMessage("Дата рождения"),
|
||||
"day": MessageLookupByLibrary.simpleMessage("День"),
|
||||
"days": MessageLookupByLibrary.simpleMessage("дней"),
|
||||
"diary": MessageLookupByLibrary.simpleMessage("Дневник"),
|
||||
"diet_author": MessageLookupByLibrary.simpleMessage("Автор диеты"),
|
||||
"diet_plan": MessageLookupByLibrary.simpleMessage("План диеты"),
|
||||
"diet_state": MessageLookupByLibrary.simpleMessage("Статус диеты"),
|
||||
"diet_time": MessageLookupByLibrary.simpleMessage("Время прохождения"),
|
||||
"diet_type": MessageLookupByLibrary.simpleMessage("Тип диеты"),
|
||||
"do_you_have_hypertension_tendency":
|
||||
MessageLookupByLibrary.simpleMessage("Есть ли у вас гипертония?"),
|
||||
"do_you_have_hypotension_tendency":
|
||||
MessageLookupByLibrary.simpleMessage("Есть ли у вас гипотония?"),
|
||||
"do_you_take_pills": MessageLookupByLibrary.simpleMessage(
|
||||
"Принимаете ли вы лекарства или бады?",),
|
||||
"down_pressure":
|
||||
MessageLookupByLibrary.simpleMessage("Нижние показатели давления:"),
|
||||
"draft": MessageLookupByLibrary.simpleMessage("Черновик"),
|
||||
"eating_behavior":
|
||||
MessageLookupByLibrary.simpleMessage("Пищевое поведение"),
|
||||
"eating_behavior_type": MessageLookupByLibrary.simpleMessage(
|
||||
"Формирование пищевого поведения",),
|
||||
"eating_behavior_type_desc": MessageLookupByLibrary.simpleMessage(
|
||||
"Формирование здорового пищевого поведения — это процесс, который включает в себя изменение привычек и отношения к еде. Он направлен на то, чтобы помочь людям научиться правильно питаться и поддерживать здоровый образ жизни.Основные принципы формирования здорового пищевого поведения: \n● Разнообразие продуктов; \n● Сбалансированный рацион; \n● Питьевой режим; \n● Ограничение вредных продуктов; \n● Регулярные приёмы пищи; \n● Умеренность;Осознанное питание; \n● Физическая активность;",),
|
||||
"economy": MessageLookupByLibrary.simpleMessage("Эконом"),
|
||||
"email": MessageLookupByLibrary.simpleMessage("Элетронная почта"),
|
||||
"email_empty": MessageLookupByLibrary.simpleMessage(
|
||||
"Необходимо ввести адрес электронной почты",),
|
||||
"email_invalid": MessageLookupByLibrary.simpleMessage(
|
||||
"Некорректный адрес электронной почты",),
|
||||
"enter_additional_information": MessageLookupByLibrary.simpleMessage(
|
||||
"Введите любую дополнительную информацию на своё усмотрение:",),
|
||||
"enter_diet_description":
|
||||
MessageLookupByLibrary.simpleMessage("Введите описание диеты"),
|
||||
"enter_diet_name":
|
||||
MessageLookupByLibrary.simpleMessage("Выведите название диеты"),
|
||||
"enter_information":
|
||||
MessageLookupByLibrary.simpleMessage("Введите информацию..."),
|
||||
"exit": MessageLookupByLibrary.simpleMessage("Выйти"),
|
||||
"expand": MessageLookupByLibrary.simpleMessage("Показать полностью"),
|
||||
"fats": MessageLookupByLibrary.simpleMessage("Жиры"),
|
||||
"favorite_foods":
|
||||
MessageLookupByLibrary.simpleMessage("Любимые продукты"),
|
||||
"favorite_unfavorite_foods": MessageLookupByLibrary.simpleMessage(
|
||||
"Ваши любимые/нелюбимые продукты питания?",),
|
||||
"female": MessageLookupByLibrary.simpleMessage("Женский"),
|
||||
"fill_in_information":
|
||||
MessageLookupByLibrary.simpleMessage("Заполните информацию:"),
|
||||
"finish": MessageLookupByLibrary.simpleMessage("Завершить"),
|
||||
"first_name": MessageLookupByLibrary.simpleMessage("Имя"),
|
||||
"first_name_empty": MessageLookupByLibrary.simpleMessage(
|
||||
"Имя обязательно для заполнения",),
|
||||
"first_name_short": MessageLookupByLibrary.simpleMessage(
|
||||
"В имени должен быть минимум 1 символ",),
|
||||
"fit_type": MessageLookupByLibrary.simpleMessage("Поддержание формы"),
|
||||
"fit_type_desc": MessageLookupByLibrary.simpleMessage(
|
||||
"План питания для поддержания формы сбалансированный и разнообразный, чтобы обеспечить организм всеми необходимыми питательными веществами. Общие принципы: \n● Дробное питание; \n● Разнообразие продуктов; \n● Контроль порций; \n● Питьевой режим; \n● Ограничение вредных продуктов;",),
|
||||
"fitness": MessageLookupByLibrary.simpleMessage("Поддержка формы"),
|
||||
"from_a_to_z": MessageLookupByLibrary.simpleMessage("Название: А-Я"),
|
||||
"from_z_to_a": MessageLookupByLibrary.simpleMessage("Название: Я-А"),
|
||||
"gallery": MessageLookupByLibrary.simpleMessage("Галерея"),
|
||||
"gr": MessageLookupByLibrary.simpleMessage("гр"),
|
||||
"grams": MessageLookupByLibrary.simpleMessage("гр"),
|
||||
"height_measurements":
|
||||
MessageLookupByLibrary.simpleMessage("Замер роста"),
|
||||
"hide": MessageLookupByLibrary.simpleMessage("Свернуть"),
|
||||
"hip_circumference":
|
||||
MessageLookupByLibrary.simpleMessage("Обхват бедер"),
|
||||
"hips": MessageLookupByLibrary.simpleMessage("Бедра"),
|
||||
"in_progress": MessageLookupByLibrary.simpleMessage("В процессе"),
|
||||
"inactive_diet_state": MessageLookupByLibrary.simpleMessage("Черновик"),
|
||||
"initial_measurements":
|
||||
MessageLookupByLibrary.simpleMessage("Первичные замеры"),
|
||||
"label": MessageLookupByLibrary.simpleMessage("Название"),
|
||||
"language": MessageLookupByLibrary.simpleMessage("Язык"),
|
||||
"last_name": MessageLookupByLibrary.simpleMessage("Фамилия"),
|
||||
"last_name_empty": MessageLookupByLibrary.simpleMessage(
|
||||
"Фамилия обязательна для заполнения",),
|
||||
"last_name_short": MessageLookupByLibrary.simpleMessage(
|
||||
"В фамилии должен быть минимум 6 символов",),
|
||||
"lbl_english": MessageLookupByLibrary.simpleMessage("English"),
|
||||
"light_type": MessageLookupByLibrary.simpleMessage("Легкая"),
|
||||
"light_type_desc": MessageLookupByLibrary.simpleMessage(
|
||||
"С некоторыми видами диет можно употреблять алкоголь в умеренных количествах. Рекомендуется совмещать вместе с низкокалорийными закусками.",),
|
||||
"link_was_copied":
|
||||
MessageLookupByLibrary.simpleMessage("Ссылка успешно скопирована"),
|
||||
"loading": MessageLookupByLibrary.simpleMessage("Загрузка..."),
|
||||
"male": MessageLookupByLibrary.simpleMessage("Мужской"),
|
||||
"meal": MessageLookupByLibrary.simpleMessage("Приём пищи"),
|
||||
"meat": MessageLookupByLibrary.simpleMessage("Мясо"),
|
||||
"meat_type": MessageLookupByLibrary.simpleMessage("С мясом"),
|
||||
"meat_type_desc": MessageLookupByLibrary.simpleMessage(
|
||||
"Диета с мясом полезная и сбалансированная. Включает в себя нежирное мясо: курицу, индейку, кролика или говядину. В сочетании с овощами получаете все необходимые витамины и минералы. Не забывайте о порциях и старайтесь есть медленно, прислушиваясь к своему организму.",),
|
||||
"milliliter": MessageLookupByLibrary.simpleMessage("мл"),
|
||||
"minimal_customer_price": MessageLookupByLibrary.simpleMessage(
|
||||
"Минимальная цена для покупателя: 1200₽",),
|
||||
"most_new": MessageLookupByLibrary.simpleMessage("Самые новые"),
|
||||
"most_old": MessageLookupByLibrary.simpleMessage("Самые старые"),
|
||||
"msg_network_err":
|
||||
MessageLookupByLibrary.simpleMessage("Сетевая ошибка!"),
|
||||
"msg_something_went_wrong":
|
||||
MessageLookupByLibrary.simpleMessage("Что то пошло не так!"),
|
||||
"my_blog": MessageLookupByLibrary.simpleMessage("Мой блог"),
|
||||
"my_settings": MessageLookupByLibrary.simpleMessage("Мои настройки"),
|
||||
"name_empty": MessageLookupByLibrary.simpleMessage(
|
||||
"Имя пользователя обязательно для заполнения",),
|
||||
"name_short": MessageLookupByLibrary.simpleMessage(
|
||||
"В имени пользователя должно быть минимум 6 символов",),
|
||||
"no": MessageLookupByLibrary.simpleMessage("Нет"),
|
||||
"no_account":
|
||||
MessageLookupByLibrary.simpleMessage("У меня нет аккаунта"),
|
||||
"one_option": MessageLookupByLibrary.simpleMessage("1 опция"),
|
||||
"option": MessageLookupByLibrary.simpleMessage("Опция"),
|
||||
"option_content":
|
||||
MessageLookupByLibrary.simpleMessage("Содержание опции"),
|
||||
"option_content_hint_text":
|
||||
MessageLookupByLibrary.simpleMessage("На связи 7 дней в неделю"),
|
||||
"option_desc": MessageLookupByLibrary.simpleMessage("Описание опции"),
|
||||
"option_desc_hint_text": MessageLookupByLibrary.simpleMessage(
|
||||
"Этот план питания для похудения на 7 дней сбалансированный и разнообразный",),
|
||||
"option_duration":
|
||||
MessageLookupByLibrary.simpleMessage("Продолжительность опции"),
|
||||
"option_personal_diet_desc": MessageLookupByLibrary.simpleMessage(
|
||||
"Опция подразумевает создание индивидуальной диеты",),
|
||||
"option_title": MessageLookupByLibrary.simpleMessage("Заголовок опции"),
|
||||
"order": MessageLookupByLibrary.simpleMessage("Порядок"),
|
||||
"password": MessageLookupByLibrary.simpleMessage("Пароль"),
|
||||
"password_confirmation":
|
||||
MessageLookupByLibrary.simpleMessage("Подтвердите пароль"),
|
||||
"password_confirmation_mismatch":
|
||||
MessageLookupByLibrary.simpleMessage("Пароли не совпадают"),
|
||||
"password_empty": MessageLookupByLibrary.simpleMessage(
|
||||
"Пароль обязателен для заполнения",),
|
||||
"password_short": MessageLookupByLibrary.simpleMessage(
|
||||
"В пароле должно быть минимум 6 символов",),
|
||||
"pcs": MessageLookupByLibrary.simpleMessage("шт"),
|
||||
"personal_diet_state":
|
||||
MessageLookupByLibrary.simpleMessage("Персональная"),
|
||||
"photo": MessageLookupByLibrary.simpleMessage("Фото"),
|
||||
"place_of_living":
|
||||
MessageLookupByLibrary.simpleMessage("Место проживания"),
|
||||
"plan_author": MessageLookupByLibrary.simpleMessage("Автор плана"),
|
||||
"plan_desc": MessageLookupByLibrary.simpleMessage("Описание плана"),
|
||||
"plan_desc_example": MessageLookupByLibrary.simpleMessage(
|
||||
"Большое подробное, привлекательное описание плана",),
|
||||
"plan_duration": MessageLookupByLibrary.simpleMessage("Срок плана"),
|
||||
"plan_label_desc":
|
||||
MessageLookupByLibrary.simpleMessage("План для поддержания формы"),
|
||||
"plans_complete":
|
||||
MessageLookupByLibrary.simpleMessage("Пройденные планы"),
|
||||
"popular": MessageLookupByLibrary.simpleMessage("Популярные"),
|
||||
"preferred_language":
|
||||
MessageLookupByLibrary.simpleMessage("Предпочитаемый язык общения"),
|
||||
"pressure_empty": MessageLookupByLibrary.simpleMessage(
|
||||
"Все показатели давления обязательны к заполнению",),
|
||||
"pressure_is_not_a_number": MessageLookupByLibrary.simpleMessage(
|
||||
"Все показатели давления должны быть числами",),
|
||||
"preview": MessageLookupByLibrary.simpleMessage("Превью"),
|
||||
"price_explanation": MessageLookupByLibrary.simpleMessage(
|
||||
"Разница в ценах, это комиссия Итвио",),
|
||||
"profile": MessageLookupByLibrary.simpleMessage("Профиль"),
|
||||
"proteins": MessageLookupByLibrary.simpleMessage("Белки"),
|
||||
"publish": MessageLookupByLibrary.simpleMessage("Опубликовать"),
|
||||
"save": MessageLookupByLibrary.simpleMessage("Сохранить"),
|
||||
"services": MessageLookupByLibrary.simpleMessage("Оказание услуг"),
|
||||
"settings": MessageLookupByLibrary.simpleMessage("Настройки"),
|
||||
"share": MessageLookupByLibrary.simpleMessage("Поделиться"),
|
||||
"skip": MessageLookupByLibrary.simpleMessage("Пропустить"),
|
||||
"slim": MessageLookupByLibrary.simpleMessage("Похудение"),
|
||||
"slim_type": MessageLookupByLibrary.simpleMessage("Похудение"),
|
||||
"slim_type_desc": MessageLookupByLibrary.simpleMessage(
|
||||
"План питания для похудения должен быть разработан с учётом индивидуальных особенностей организма, возраста, пола, уровня физической активности и состояния здоровья. Общие принципы: \n● Снижение калорийности; \n● Сбалансированный рацион; \n● Разнообразие продуктов; \n● Питьевой режим; \n● Ограничение вредных продуктов;",),
|
||||
"standard": MessageLookupByLibrary.simpleMessage("Стандарт"),
|
||||
"startDiet": MessageLookupByLibrary.simpleMessage("Начать диету"),
|
||||
"tell_your_schedule": MessageLookupByLibrary.simpleMessage(
|
||||
"Расскажите в свободной форме о том как просыпаетесь, когда едите, когда ложитесь спать:",),
|
||||
"three_options": MessageLookupByLibrary.simpleMessage("3 опции"),
|
||||
"to_draft": MessageLookupByLibrary.simpleMessage("В черновик"),
|
||||
"unfavorite_foods":
|
||||
MessageLookupByLibrary.simpleMessage("Нелюбимые продукты"),
|
||||
"up_pressure": MessageLookupByLibrary.simpleMessage(
|
||||
"Верхние показатели давления:",),
|
||||
"user_authorization":
|
||||
MessageLookupByLibrary.simpleMessage("Аутентификация"),
|
||||
"user_registration":
|
||||
MessageLookupByLibrary.simpleMessage("Регистрация пользователя"),
|
||||
"username": MessageLookupByLibrary.simpleMessage("Имя пользователя"),
|
||||
"vegan": MessageLookupByLibrary.simpleMessage("Веган."),
|
||||
"vegan_type": MessageLookupByLibrary.simpleMessage("Вегетарианская"),
|
||||
"vegan_type_desc": MessageLookupByLibrary.simpleMessage(
|
||||
"Диета основана на потреблении растительной пищи и отказе от продуктов животного происхождения. Такой тип питания может быть разнообразным и вкусным, а также полезным для здоровья.",),
|
||||
"waist": MessageLookupByLibrary.simpleMessage("Талия"),
|
||||
"waist_circumference":
|
||||
MessageLookupByLibrary.simpleMessage("Обхват талии"),
|
||||
"weight": MessageLookupByLibrary.simpleMessage("Вес"),
|
||||
"weight_chart": MessageLookupByLibrary.simpleMessage("График веса"),
|
||||
"weight_measurements":
|
||||
MessageLookupByLibrary.simpleMessage("Замер веса"),
|
||||
"wellness":
|
||||
MessageLookupByLibrary.simpleMessage("Улучшение самочувствия"),
|
||||
"wellness_type":
|
||||
MessageLookupByLibrary.simpleMessage("Улучшение самочувствия"),
|
||||
"wellness_type_desc": MessageLookupByLibrary.simpleMessage(
|
||||
"Если вы любите мучные продукты, но хотите придерживаться диеты, вам подходит диета с добавленим мучных блюд на основе цельнозерновых вариантов. В них больше сложных углеводов и клетчатки, что способствует длительному чувству сытости и поддерживает здоровье пищеварительной системы.",),
|
||||
"what_is_your_name":
|
||||
MessageLookupByLibrary.simpleMessage("Как вас зовут?"),
|
||||
"wrong_credentials": MessageLookupByLibrary.simpleMessage(
|
||||
"Неверные данные пользователя",),
|
||||
"you_will_get": MessageLookupByLibrary.simpleMessage("Вы получите"),
|
||||
"your_blood_pressure":
|
||||
MessageLookupByLibrary.simpleMessage("Ваше артериальное давление?"),
|
||||
"your_favorite_sports":
|
||||
MessageLookupByLibrary.simpleMessage("Ваши спортивные увлечения?"),
|
||||
"your_gender": MessageLookupByLibrary.simpleMessage("Ваш пол?"),
|
||||
"your_height": MessageLookupByLibrary.simpleMessage("Ваш рост:"),
|
||||
"your_schedule": MessageLookupByLibrary.simpleMessage("Ваш режим дня?"),
|
||||
"your_weight": MessageLookupByLibrary.simpleMessage("Ваш вес:"),
|
||||
};
|
||||
}
|
||||
@@ -2177,6 +2177,7 @@ class AppLocalizationDelegate extends LocalizationsDelegate<S> {
|
||||
List<Locale> get supportedLocales {
|
||||
return const <Locale>[
|
||||
Locale.fromSubtags(languageCode: 'en'),
|
||||
Locale.fromSubtags(languageCode: 'ru'),
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
@@ -98,14 +98,14 @@
|
||||
"gr": "гр",
|
||||
"milliliter": "мл",
|
||||
"grams": "гр",
|
||||
"startDiet": "Начать диету",
|
||||
"startDiet": "Начать диету",
|
||||
"comments": "Комментарии",
|
||||
"comment": "комментарий",
|
||||
"another_form_of_comment": "комментария",
|
||||
"about_diet": "Об этой диете",
|
||||
"days": "дней",
|
||||
"day": "День",
|
||||
"another_form_of_day": "дня",
|
||||
"another_form_of_day": "дня",
|
||||
"comments_plural": "комментариев",
|
||||
"meal": "Приём пищи",
|
||||
"diet_author": "Автор диеты",
|
||||
@@ -113,7 +113,7 @@
|
||||
"expand": "Показать полностью",
|
||||
"copy_link": "Скопировать ссылку",
|
||||
"share": "Поделиться",
|
||||
"complain": "Пожаловаться",
|
||||
"complain": "Пожаловаться",
|
||||
"vegan_type": "Вегетарианская",
|
||||
"light_type": "Легкая",
|
||||
"meat_type": "С мясом",
|
||||
@@ -127,7 +127,7 @@
|
||||
"proteins": "Белки",
|
||||
"fats": "Жиры",
|
||||
"carbohydrates": "Углеводы",
|
||||
"answer": "Ответить",
|
||||
"answer": "Ответить",
|
||||
"link_was_copied": "Ссылка успешно скопирована",
|
||||
"choose_start_diet_date": "Выберите дату старты диеты. Она отобразиться у Вас в дневнике",
|
||||
"continue_": "Продолжить",
|
||||
@@ -150,7 +150,7 @@
|
||||
"active_diet_state": "Активная",
|
||||
"inactive_diet_state": "Черновик",
|
||||
"personal_diet_state": "Персональная",
|
||||
"meat": "Мясо",
|
||||
"meat": "Мясо",
|
||||
"vegan": "Веган.",
|
||||
"photo": "Фото",
|
||||
"gallery": "Галерея",
|
||||
|
||||
201
lib/l10n/intl_ru.arb
Normal file
201
lib/l10n/intl_ru.arb
Normal file
@@ -0,0 +1,201 @@
|
||||
{
|
||||
"user_registration": "Регистрация пользователя",
|
||||
"email": "Элетронная почта",
|
||||
"username": "Имя пользователя",
|
||||
"password": "Пароль",
|
||||
"password_confirmation": "Подтвердите пароль",
|
||||
"wrong_credentials": "Неверные данные пользователя",
|
||||
"first_name": "Имя",
|
||||
"last_name": "Фамилия",
|
||||
"what_is_your_name": "Как вас зовут?",
|
||||
"date_of_birth": "Дата рождения",
|
||||
"country": "Страна",
|
||||
"Russia": "Россия",
|
||||
"city": "Город",
|
||||
"Moscow": "Москва",
|
||||
"place_of_living": "Место проживания",
|
||||
"hip_circumference": "Обхват бедер",
|
||||
"chest_circumference": "Обхват груди",
|
||||
"waist_circumference": "Обхват талии",
|
||||
"initial_measurements": "Первичные замеры",
|
||||
"male": "Мужской",
|
||||
"female": "Женский",
|
||||
"weight_measurements": "Замер веса",
|
||||
"your_weight": "Ваш вес:",
|
||||
"height_measurements": "Замер роста",
|
||||
"your_height": "Ваш рост:",
|
||||
"allergies_presence": "Наличие аллергии",
|
||||
"do_you_take_pills": "Принимаете ли вы лекарства или бады?",
|
||||
"your_gender": "Ваш пол?",
|
||||
"no": "Нет",
|
||||
"do_you_have_hypertension_tendency": "Есть ли у вас гипертония?",
|
||||
"your_blood_pressure": "Ваше артериальное давление?",
|
||||
"average_pressure": "Средние показатели давления:",
|
||||
"up_pressure": "Верхние показатели давления:",
|
||||
"down_pressure": "Нижние показатели давления:",
|
||||
"do_you_have_hypotension_tendency": "Есть ли у вас гипотония?",
|
||||
"your_favorite_sports": "Ваши спортивные увлечения?",
|
||||
"favorite_foods": "Любимые продукты",
|
||||
"favorite_unfavorite_foods": "Ваши любимые/нелюбимые продукты питания?",
|
||||
"unfavorite_foods": "Нелюбимые продукты",
|
||||
"your_schedule": "Ваш режим дня?",
|
||||
"tell_your_schedule": "Расскажите в свободной форме о том как просыпаетесь, когда едите, когда ложитесь спать:",
|
||||
"additional_information": "Дополнительная информация",
|
||||
"enter_additional_information": "Введите любую дополнительную информацию на своё усмотрение:",
|
||||
"lbl_english": "English",
|
||||
"finish": "Завершить",
|
||||
"preferred_language": "Предпочитаемый язык общения",
|
||||
"continueButton": "Продолжить",
|
||||
"skip": "Пропустить",
|
||||
"fill_in_information": "Заполните информацию:",
|
||||
"enter_information": "Введите информацию...",
|
||||
"msg_network_err": "Сетевая ошибка!",
|
||||
"msg_something_went_wrong": "Что то пошло не так!",
|
||||
"email_empty": "Необходимо ввести адрес электронной почты",
|
||||
"email_invalid": "Некорректный адрес электронной почты",
|
||||
"password_empty": "Пароль обязателен для заполнения",
|
||||
"password_short": "В пароле должно быть минимум 6 символов",
|
||||
"name_empty": "Имя пользователя обязательно для заполнения",
|
||||
"name_short": "В имени пользователя должно быть минимум 6 символов",
|
||||
"password_confirmation_mismatch": "Пароли не совпадают",
|
||||
"first_name_empty": "Имя обязательно для заполнения",
|
||||
"first_name_short": "В имени должен быть минимум 1 символ",
|
||||
"last_name_empty": "Фамилия обязательна для заполнения",
|
||||
"last_name_short": "В фамилии должен быть минимум 6 символов",
|
||||
"circumference_empty": "Все замеры обязательны к заполнению",
|
||||
"circumference_is_not_a_number": "Все замеры должны быть числами",
|
||||
"pressure_empty": "Все показатели давления обязательны к заполнению",
|
||||
"pressure_is_not_a_number": "Все показатели давления должны быть числами",
|
||||
"no_account": "У меня нет аккаунта",
|
||||
"authentificate": "Войти",
|
||||
"user_authorization": "Аутентификация",
|
||||
"loading": "Загрузка...",
|
||||
"weight_chart": "График веса",
|
||||
"circumferences_chart": "График обхватов",
|
||||
"city_of_living": "Город проживания",
|
||||
"bodymass_chart": "График ИМТ",
|
||||
"chest": "Грудь",
|
||||
"waist": "Талия",
|
||||
"hips": "Бедра",
|
||||
"exit": "Выйти",
|
||||
"active": "Активен",
|
||||
"archived": "В архиве",
|
||||
"draft": "Черновик",
|
||||
"my_blog": "Мой блог",
|
||||
"language": "Язык",
|
||||
"in_progress": "В процессе",
|
||||
"add_measure": "Добавить замер",
|
||||
"weight": "Вес",
|
||||
"plans_complete": "Пройденные планы",
|
||||
"my_settings": "Мои настройки",
|
||||
"settings": "Настройки",
|
||||
"profile": "Профиль",
|
||||
"chat": "Чат",
|
||||
"admin": "Админ",
|
||||
"diary": "Дневник",
|
||||
"save": "Сохранить",
|
||||
"pcs": "шт",
|
||||
"gr": "гр",
|
||||
"milliliter": "мл",
|
||||
"grams": "гр",
|
||||
"startDiet": "Начать диету",
|
||||
"comments": "Комментарии",
|
||||
"comment": "комментарий",
|
||||
"another_form_of_comment": "комментария",
|
||||
"about_diet": "Об этой диете",
|
||||
"days": "дней",
|
||||
"day": "День",
|
||||
"another_form_of_day": "дня",
|
||||
"comments_plural": "комментариев",
|
||||
"meal": "Приём пищи",
|
||||
"diet_author": "Автор диеты",
|
||||
"hide": "Свернуть",
|
||||
"expand": "Показать полностью",
|
||||
"copy_link": "Скопировать ссылку",
|
||||
"share": "Поделиться",
|
||||
"complain": "Пожаловаться",
|
||||
"vegan_type": "Вегетарианская",
|
||||
"light_type": "Легкая",
|
||||
"meat_type": "С мясом",
|
||||
"carb_type": "Мучное",
|
||||
"vegan_type_desc": "Диета основана на потреблении растительной пищи и отказе от продуктов животного происхождения. Такой тип питания может быть разнообразным и вкусным, а также полезным для здоровья.",
|
||||
"light_type_desc": "С некоторыми видами диет можно употреблять алкоголь в умеренных количествах. Рекомендуется совмещать вместе с низкокалорийными закусками.",
|
||||
"carb_type_desc": "Если вы любите мучные продукты, но хотите придерживаться диеты, вам подходит диета с добавленим мучных блюд на основе цельнозерновых вариантов. В них больше сложных углеводов и клетчатки, что способствует длительному чувству сытости и поддерживает здоровье пищеварительной системы.",
|
||||
"meat_type_desc": "Диета с мясом полезная и сбалансированная. Включает в себя нежирное мясо: курицу, индейку, кролика или говядину. В сочетании с овощами получаете все необходимые витамины и минералы. Не забывайте о порциях и старайтесь есть медленно, прислушиваясь к своему организму.",
|
||||
"diet_plan": "План диеты",
|
||||
"calories": "Калории",
|
||||
"proteins": "Белки",
|
||||
"fats": "Жиры",
|
||||
"carbohydrates": "Углеводы",
|
||||
"answer": "Ответить",
|
||||
"link_was_copied": "Ссылка успешно скопирована",
|
||||
"choose_start_diet_date": "Выберите дату старты диеты. Она отобразиться у Вас в дневнике",
|
||||
"continue_": "Продолжить",
|
||||
"order": "Порядок",
|
||||
"diet_time": "Время прохождения",
|
||||
"all": "Все",
|
||||
"diet_type": "Тип диеты",
|
||||
"popular": "Популярные",
|
||||
"most_new": "Самые новые",
|
||||
"most_old": "Самые старые",
|
||||
"from_a_to_z": "Название: А-Я",
|
||||
"from_z_to_a": "Название: Я-А",
|
||||
"by_default": "По умолчянию",
|
||||
"create_diet": "Создание диеты",
|
||||
"enter_diet_name": "Выведите название диеты",
|
||||
"enter_diet_description": "Введите описание диеты",
|
||||
"choose_diet_duration": "Выберите срок диеты",
|
||||
"diet_state": "Статус диеты",
|
||||
"active_diet_state": "Активная",
|
||||
"inactive_diet_state": "Черновик",
|
||||
"personal_diet_state": "Персональная",
|
||||
"meat": "Мясо",
|
||||
"vegan": "Веган.",
|
||||
"photo": "Фото",
|
||||
"gallery": "Галерея",
|
||||
"add_cover": "Добавьте обложку",
|
||||
"add": "Добавить",
|
||||
"copied": "cкопирован",
|
||||
"to_draft": "В черновик",
|
||||
"preview": "Превью",
|
||||
"publish": "Опубликовать",
|
||||
"adding_plan": "Добавление плана",
|
||||
"label": "Название",
|
||||
"plan_label_desc": "План для поддержания формы",
|
||||
"plan_duration": "Срок плана",
|
||||
"slim": "Похудение",
|
||||
"fitness": "Поддержка формы",
|
||||
"wellness": "Улучшение самочувствия",
|
||||
"eating_behavior": "Пищевое поведение",
|
||||
"choose_plan_duration": "Выберите срок плана",
|
||||
"plan_desc": "Описание плана",
|
||||
"plan_desc_example": "Большое подробное, привлекательное описание плана",
|
||||
"one_option": "1 опция",
|
||||
"three_options": "3 опции",
|
||||
"option": "Опция",
|
||||
"services": "Оказание услуг",
|
||||
"economy": "Эконом",
|
||||
"standard": "Стандарт",
|
||||
"comfort": "Комфорт",
|
||||
"option_title": "Заголовок опции",
|
||||
"option_desc": "Описание опции",
|
||||
"option_desc_hint_text": "Этот план питания для похудения на 7 дней сбалансированный и разнообразный",
|
||||
"you_will_get": "Вы получите",
|
||||
"customer_price": "Цена для покупателя",
|
||||
"minimal_customer_price": "Минимальная цена для покупателя: 1200₽",
|
||||
"price_explanation": "Разница в ценах, это комиссия Итвио",
|
||||
"option_content": "Содержание опции",
|
||||
"option_content_hint_text": "На связи 7 дней в неделю",
|
||||
"option_personal_diet_desc": "Опция подразумевает создание индивидуальной диеты",
|
||||
"about_plan": "Об этом плане",
|
||||
"plan_author": "Автор плана",
|
||||
"option_duration": "Продолжительность опции",
|
||||
"fit_type": "Поддержание формы",
|
||||
"fit_type_desc": "План питания для поддержания формы сбалансированный и разнообразный, чтобы обеспечить организм всеми необходимыми питательными веществами. Общие принципы: \n● Дробное питание; \n● Разнообразие продуктов; \n● Контроль порций; \n● Питьевой режим; \n● Ограничение вредных продуктов;",
|
||||
"slim_type": "Похудение",
|
||||
"slim_type_desc": "План питания для похудения должен быть разработан с учётом индивидуальных особенностей организма, возраста, пола, уровня физической активности и состояния здоровья. Общие принципы: \n● Снижение калорийности; \n● Сбалансированный рацион; \n● Разнообразие продуктов; \n● Питьевой режим; \n● Ограничение вредных продуктов;",
|
||||
"wellness_type": "Улучшение самочувствия",
|
||||
"wellness_type_desc": "Если вы любите мучные продукты, но хотите придерживаться диеты, вам подходит диета с добавленим мучных блюд на основе цельнозерновых вариантов. В них больше сложных углеводов и клетчатки, что способствует длительному чувству сытости и поддерживает здоровье пищеварительной системы.",
|
||||
"eating_behavior_type": "Формирование пищевого поведения",
|
||||
"eating_behavior_type_desc": "Формирование здорового пищевого поведения — это процесс, который включает в себя изменение привычек и отношения к еде. Он направлен на то, чтобы помочь людям научиться правильно питаться и поддерживать здоровый образ жизни.Основные принципы формирования здорового пищевого поведения: \n● Разнообразие продуктов; \n● Сбалансированный рацион; \n● Питьевой режим; \n● Ограничение вредных продуктов; \n● Регулярные приёмы пищи; \n● Умеренность;Осознанное питание; \n● Физическая активность;"
|
||||
}
|
||||
@@ -45,7 +45,9 @@ class MyApp extends StatelessWidget {
|
||||
GlobalWidgetsLocalizations.delegate,
|
||||
GlobalCupertinoLocalizations.delegate,
|
||||
],
|
||||
supportedLocales: S.delegate.supportedLocales,
|
||||
supportedLocales: [
|
||||
Locale.fromSubtags(languageCode: 'ru'),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
12
pubspec.yaml
12
pubspec.yaml
@@ -29,7 +29,7 @@ dependencies:
|
||||
go_router: ^14.6.2
|
||||
keyboard_dismisser: ^3.0.0
|
||||
retrofit: ^4.4.1
|
||||
get_it: ^8.0.2
|
||||
get_it: ^8.0.3
|
||||
dio: ^5.7.0
|
||||
dart_pusher_channels: ^1.2.3
|
||||
photo_view: ^0.15.0
|
||||
@@ -39,11 +39,11 @@ dependencies:
|
||||
badges: ^3.1.2
|
||||
google_fonts: ^6.2.1
|
||||
location: ^7.0.1
|
||||
syncfusion_flutter_charts: ^27.2.5
|
||||
syncfusion_flutter_gauges: ^27.2.5
|
||||
table_calendar: ^3.1.2
|
||||
syncfusion_flutter_charts: ^28.1.33
|
||||
syncfusion_flutter_gauges: ^28.1.33
|
||||
table_calendar: ^3.1.3
|
||||
flutter_keyboard_visibility: ^6.0.0
|
||||
share_plus: ^10.1.2
|
||||
share_plus: ^10.1.3
|
||||
flutter_html: ^3.0.0-beta.2
|
||||
file_picker:
|
||||
git:
|
||||
@@ -64,7 +64,7 @@ dependencies:
|
||||
http: ^1.2.2
|
||||
|
||||
dev_dependencies:
|
||||
build_runner: ^2.4.13
|
||||
build_runner: ^2.4.14
|
||||
freezed: ^2.5.7
|
||||
json_serializable: ^6.9.0
|
||||
retrofit_generator: ^9.1.5
|
||||
|
||||
Reference in New Issue
Block a user