diff --git a/assets/icons/add_step_photo.svg b/assets/icons/add_step_photo.svg new file mode 100644 index 00000000..2967b460 --- /dev/null +++ b/assets/icons/add_step_photo.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/assets/icons/upload_photo_of_portion.svg b/assets/icons/upload_photo_of_portion.svg new file mode 100644 index 00000000..3149160f --- /dev/null +++ b/assets/icons/upload_photo_of_portion.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/lib/core/app_router.dart b/lib/core/app_router.dart index b41077d8..137613c4 100644 --- a/lib/core/app_router.dart +++ b/lib/core/app_router.dart @@ -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', diff --git a/lib/core/const.dart b/lib/core/const.dart index 273ac73d..24e72cdd 100644 --- a/lib/core/const.dart +++ b/lib/core/const.dart @@ -1,4 +1,4 @@ -const String baseUrl = 'https://dev2.eatvio.com'; +String baseUrl = 'https://dev2.eatvio.com'; const List durationValues = [7, 14, 21]; diff --git a/lib/core/locator_service.dart b/lib/core/locator_service.dart index 4a6bc28a..5fa33479 100644 --- a/lib/core/locator_service.dart +++ b/lib/core/locator_service.dart @@ -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 init() async { sl.registerFactory(() => CalendarCubit(calendarRepository: sl())); sl.registerFactory(() => ProfileCubit(profileRepository: sl(), chatRepository: sl())); sl.registerFactory(() => profilePlans.PlansCubit(plansRepository: sl())); - sl.registerFactory(() => RecipeCreateCubit(recipeRepository: sl())); sl.registerFactory(() => DietsCubit(feedRepository: sl(), dietsRepository: sl())); sl.registerFactory(() => CommentsCubit(feedRepository: sl())); sl.registerFactory(() => ChatSelectCubit(chatRepository: sl())); @@ -96,6 +95,7 @@ Future init() async { sl.registerFactory(() => CreatePlanCubit()); sl.registerFactory(() => FeedProfileCubit(profileRepository: sl())); sl.registerFactory(() => AboutPlanCubit(planRepository: sl())); + sl.registerFactory(() => CreateRecipeCubit()); sl.registerFactory(() => MyDietsCubit(myDietsRepository: sl())); sl.registerFactory(() => MyPlansCubit(myPlansRepository: sl())); diff --git a/lib/core/widgets/custom_drop_down.dart b/lib/core/widgets/custom_drop_down.dart index 887f4597..10edb929 100644 --- a/lib/core/widgets/custom_drop_down.dart +++ b/lib/core/widgets/custom_drop_down.dart @@ -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( value: item, diff --git a/lib/core/widgets/custom_text_form_field.dart b/lib/core/widgets/custom_text_form_field.dart index f343e996..653600b3 100644 --- a/lib/core/widgets/custom_text_form_field.dart +++ b/lib/core/widgets/custom_text_form_field.dart @@ -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, diff --git a/lib/features/auth/auth_screen/auth_screen.dart b/lib/features/auth/auth_screen/auth_screen.dart index 94914e51..78cfdfc1 100644 --- a/lib/features/auth/auth_screen/auth_screen.dart +++ b/lib/features/auth/auth_screen/auth_screen.dart @@ -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 { bool checkboxState = false; bool hidePassword = true; + final baseUrlController = TextEditingController(); @override Widget build(BuildContext context) { @@ -49,10 +51,54 @@ class _AuthScreenState extends State { 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().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( diff --git a/lib/features/diary/presentation/pages/diary_screen.dart b/lib/features/diary/presentation/pages/diary_screen.dart index c6c8e291..24f916ac 100644 --- a/lib/features/diary/presentation/pages/diary_screen.dart +++ b/lib/features/diary/presentation/pages/diary_screen.dart @@ -35,6 +35,7 @@ class _DiaryScreenState extends State { CalendarWidget(), if (state is DiaryStateLoading) const Loader(), if (state is DiaryStateLoaded) MealWidget(), + const SizedBox(height: 12), CaloriesChartWidget(), ], ), diff --git a/lib/features/diary/presentation/widgets/calories_chart_widget.dart b/lib/features/diary/presentation/widgets/calories_chart_widget.dart index d59d3b24..ddb7cbb8 100644 --- a/lib/features/diary/presentation/widgets/calories_chart_widget.dart +++ b/lib/features/diary/presentation/widgets/calories_chart_widget.dart @@ -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, diff --git a/lib/features/feed/presentation/pages/feed_screen.dart b/lib/features/feed/presentation/pages/feed_screen.dart index c5852416..07e73077 100644 --- a/lib/features/feed/presentation/pages/feed_screen.dart +++ b/lib/features/feed/presentation/pages/feed_screen.dart @@ -215,6 +215,9 @@ class _FeedScreenState extends State { ), 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, ), diff --git a/lib/features/menu/presentation/pages/menu_screen.dart b/lib/features/menu/presentation/pages/menu_screen.dart index 50c3d308..ad974e54 100644 --- a/lib/features/menu/presentation/pages/menu_screen.dart +++ b/lib/features/menu/presentation/pages/menu_screen.dart @@ -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) { diff --git a/lib/features/profile/presentation/pages/profile_screen.dart b/lib/features/profile/presentation/pages/profile_screen.dart index 76831727..dcf78172 100644 --- a/lib/features/profile/presentation/pages/profile_screen.dart +++ b/lib/features/profile/presentation/pages/profile_screen.dart @@ -192,10 +192,10 @@ class _ProfileScreenState extends State { ), _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 { ), _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 { 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 { } _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 { } _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 { 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 { ); }, '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 { ); }, 'assets/icons/web.svg', + state.profile.educationResource?.personalSiteLink == null ? true : false, ), ], ), @@ -586,9 +598,9 @@ class _ProfileScreenState extends State { ); } - _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 { child: SvgPicture.asset( icon, width: 24, + colorFilter: ColorFilter.mode(Colors.black12, BlendMode.srcIn), ), ), ), diff --git a/lib/features/recipes/entities/recipe_create_entity.dart b/lib/features/recipes/entities/recipe_create_entity.dart new file mode 100644 index 00000000..43a44a55 --- /dev/null +++ b/lib/features/recipes/entities/recipe_create_entity.dart @@ -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 ingredients, + required List steps, + required List 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; +} diff --git a/lib/features/recipes/entities/recipe_create_entity.freezed.dart b/lib/features/recipes/entities/recipe_create_entity.freezed.dart new file mode 100644 index 00000000..e42db8f4 --- /dev/null +++ b/lib/features/recipes/entities/recipe_create_entity.freezed.dart @@ -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 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 get ingredients => throw _privateConstructorUsedError; + List get steps => throw _privateConstructorUsedError; + List 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 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 ingredients, + List steps, + List 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, + steps: null == steps + ? _value.steps + : steps // ignore: cast_nullable_to_non_nullable + as List, + tags: null == tags + ? _value.tags + : tags // ignore: cast_nullable_to_non_nullable + as List, + ) 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 ingredients, + List steps, + List 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, + steps: null == steps + ? _value._steps + : steps // ignore: cast_nullable_to_non_nullable + as List, + tags: null == tags + ? _value._tags + : tags // ignore: cast_nullable_to_non_nullable + as List, + )); + } +} + +/// @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 ingredients, + required final List steps, + required final List 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 _ingredients; + @override + List get ingredients { + if (_ingredients is EqualUnmodifiableListView) return _ingredients; + // ignore: implicit_dynamic_type + return EqualUnmodifiableListView(_ingredients); + } + + final List _steps; + @override + List get steps { + if (_steps is EqualUnmodifiableListView) return _steps; + // ignore: implicit_dynamic_type + return EqualUnmodifiableListView(_steps); + } + + final List _tags; + @override + List 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 ingredients, + required final List steps, + required final List 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 get ingredients; + @override + List get steps; + @override + List 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 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 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; +} diff --git a/lib/features/recipes/presentation/bloc/create_recipe_cubit/create_recipe_cubit.dart b/lib/features/recipes/presentation/bloc/create_recipe_cubit/create_recipe_cubit.dart new file mode 100644 index 00000000..03253f26 --- /dev/null +++ b/lib/features/recipes/presentation/bloc/create_recipe_cubit/create_recipe_cubit.dart @@ -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 { + CreateRecipeCubit() + : super( + CreateRecipeState( + RecipeCreateEntity( + ingredients: [], + steps: [], + tags: [], + ), + ), + ); + + addStep() { + emit( + CreateRecipeState( + state.entity.copyWith( + steps: [ + ...state.entity.steps, + StepEntity(), + ], + ), + ), + ); + } + + removeStep(StepEntity step) { + List 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 ingredients = [...state.entity.ingredients]; + ingredients.removeAt(index); + + emit( + CreateRecipeState( + state.entity.copyWith( + ingredients: ingredients, + ), + ), + ); + } + + replaceIngredientPosition(int oldIndex, int newIndex) { + List 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 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 steps = [...state.entity.steps]; + steps[index] = steps[index].copyWith(description: description); + emit( + CreateRecipeState( + state.entity.copyWith( + steps: steps, + ), + ), + ); + } + + replaceImageInStep(File? image, int index) { + List steps = [...state.entity.steps]; + steps[index] = steps[index].copyWith(image: image); + emit( + CreateRecipeState( + state.entity.copyWith( + steps: steps, + ), + ), + ); + } +} diff --git a/lib/features/recipes/presentation/bloc/create_recipe_cubit/create_recipe_state.dart b/lib/features/recipes/presentation/bloc/create_recipe_cubit/create_recipe_state.dart new file mode 100644 index 00000000..19adabf8 --- /dev/null +++ b/lib/features/recipes/presentation/bloc/create_recipe_cubit/create_recipe_state.dart @@ -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; +} diff --git a/lib/features/recipes/presentation/bloc/create_recipe_cubit/create_recipe_state.freezed.dart b/lib/features/recipes/presentation/bloc/create_recipe_cubit/create_recipe_state.freezed.dart new file mode 100644 index 00000000..b1661b76 --- /dev/null +++ b/lib/features/recipes/presentation/bloc/create_recipe_cubit/create_recipe_state.freezed.dart @@ -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 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 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; +} diff --git a/lib/features/recipes/presentation/bloc/recipe_create_cubit.dart b/lib/features/recipes/presentation/bloc/recipe_create_cubit.dart deleted file mode 100644 index e0cd0dce..00000000 --- a/lib/features/recipes/presentation/bloc/recipe_create_cubit.dart +++ /dev/null @@ -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 { - 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 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 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 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 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 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 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.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 newIngredients = List.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 newStepImages = List.from(currentState.stepImages); - newStepImages.add(null); - List newStepControllers = List.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 newStepImages = List.from(currentState.stepImages); - newStepImages.removeLast(); - List newStepControllers = List.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> 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); - } - } -} diff --git a/lib/features/recipes/presentation/bloc/recipe_create_state.dart b/lib/features/recipes/presentation/bloc/recipe_create_state.dart deleted file mode 100644 index 8e84b19b..00000000 --- a/lib/features/recipes/presentation/bloc/recipe_create_state.dart +++ /dev/null @@ -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 stepControllers, - XFile? recipeImage, - required List stepImages, - required List ingredients, - required List autocompleteList, - required int numberOfSteps, - required bool ingredientAsItems, - required List types, - required List cookingTimes, - required List statuses, - required SelectionPopupModel selectedType, - required SelectionPopupModel selectedCookingTime, - required SelectionPopupModel selectedStatus, - }) = RecipeCreateStateLoaded; - - const factory RecipeCreateState.error({ - required String errorMessage, - }) = RecipeCreateStateError; -} diff --git a/lib/features/recipes/presentation/bloc/recipe_create_state.freezed.dart b/lib/features/recipes/presentation/bloc/recipe_create_state.freezed.dart deleted file mode 100644 index 672872e2..00000000 --- a/lib/features/recipes/presentation/bloc/recipe_create_state.freezed.dart +++ /dev/null @@ -1,1386 +0,0 @@ -// 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_state.dart'; - -// ************************************************************************** -// FreezedGenerator -// ************************************************************************** - -T _$identity(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 _$RecipeCreateState { - @optionalTypeArgs - TResult when( - TResult Function() $default, { - required TResult Function() loading, - required TResult Function( - RecipeModel? recipe, - ImagePicker picker, - TextEditingController titleController, - TextEditingController productNameController, - TextEditingController ingredientController, - TextEditingController stepDescriptionController, - TextEditingController videoLinkController, - List stepControllers, - XFile? recipeImage, - List stepImages, - List ingredients, - List autocompleteList, - int numberOfSteps, - bool ingredientAsItems, - List types, - List cookingTimes, - List statuses, - SelectionPopupModel selectedType, - SelectionPopupModel selectedCookingTime, - SelectionPopupModel selectedStatus) - loaded, - required TResult Function(String errorMessage) error, - }) => - throw _privateConstructorUsedError; - @optionalTypeArgs - TResult? whenOrNull( - TResult? Function()? $default, { - TResult? Function()? loading, - TResult? Function( - RecipeModel? recipe, - ImagePicker picker, - TextEditingController titleController, - TextEditingController productNameController, - TextEditingController ingredientController, - TextEditingController stepDescriptionController, - TextEditingController videoLinkController, - List stepControllers, - XFile? recipeImage, - List stepImages, - List ingredients, - List autocompleteList, - int numberOfSteps, - bool ingredientAsItems, - List types, - List cookingTimes, - List statuses, - SelectionPopupModel selectedType, - SelectionPopupModel selectedCookingTime, - SelectionPopupModel selectedStatus)? - loaded, - TResult? Function(String errorMessage)? error, - }) => - throw _privateConstructorUsedError; - @optionalTypeArgs - TResult maybeWhen( - TResult Function()? $default, { - TResult Function()? loading, - TResult Function( - RecipeModel? recipe, - ImagePicker picker, - TextEditingController titleController, - TextEditingController productNameController, - TextEditingController ingredientController, - TextEditingController stepDescriptionController, - TextEditingController videoLinkController, - List stepControllers, - XFile? recipeImage, - List stepImages, - List ingredients, - List autocompleteList, - int numberOfSteps, - bool ingredientAsItems, - List types, - List cookingTimes, - List statuses, - SelectionPopupModel selectedType, - SelectionPopupModel selectedCookingTime, - SelectionPopupModel selectedStatus)? - loaded, - TResult Function(String errorMessage)? error, - required TResult orElse(), - }) => - throw _privateConstructorUsedError; - @optionalTypeArgs - TResult map( - TResult Function(_RecipeCreateState value) $default, { - required TResult Function(RecipeCreateStateLoading value) loading, - required TResult Function(RecipeCreateStateLoaded value) loaded, - required TResult Function(RecipeCreateStateError value) error, - }) => - throw _privateConstructorUsedError; - @optionalTypeArgs - TResult? mapOrNull( - TResult? Function(_RecipeCreateState value)? $default, { - TResult? Function(RecipeCreateStateLoading value)? loading, - TResult? Function(RecipeCreateStateLoaded value)? loaded, - TResult? Function(RecipeCreateStateError value)? error, - }) => - throw _privateConstructorUsedError; - @optionalTypeArgs - TResult maybeMap( - TResult Function(_RecipeCreateState value)? $default, { - TResult Function(RecipeCreateStateLoading value)? loading, - TResult Function(RecipeCreateStateLoaded value)? loaded, - TResult Function(RecipeCreateStateError value)? error, - required TResult orElse(), - }) => - throw _privateConstructorUsedError; -} - -/// @nodoc -abstract class $RecipeCreateStateCopyWith<$Res> { - factory $RecipeCreateStateCopyWith( - RecipeCreateState value, $Res Function(RecipeCreateState) then) = - _$RecipeCreateStateCopyWithImpl<$Res, RecipeCreateState>; -} - -/// @nodoc -class _$RecipeCreateStateCopyWithImpl<$Res, $Val extends RecipeCreateState> - implements $RecipeCreateStateCopyWith<$Res> { - _$RecipeCreateStateCopyWithImpl(this._value, this._then); - - // ignore: unused_field - final $Val _value; - // ignore: unused_field - final $Res Function($Val) _then; - - /// Create a copy of RecipeCreateState - /// with the given fields replaced by the non-null parameter values. -} - -/// @nodoc -abstract class _$$RecipeCreateStateImplCopyWith<$Res> { - factory _$$RecipeCreateStateImplCopyWith(_$RecipeCreateStateImpl value, - $Res Function(_$RecipeCreateStateImpl) then) = - __$$RecipeCreateStateImplCopyWithImpl<$Res>; -} - -/// @nodoc -class __$$RecipeCreateStateImplCopyWithImpl<$Res> - extends _$RecipeCreateStateCopyWithImpl<$Res, _$RecipeCreateStateImpl> - implements _$$RecipeCreateStateImplCopyWith<$Res> { - __$$RecipeCreateStateImplCopyWithImpl(_$RecipeCreateStateImpl _value, - $Res Function(_$RecipeCreateStateImpl) _then) - : super(_value, _then); - - /// Create a copy of RecipeCreateState - /// with the given fields replaced by the non-null parameter values. -} - -/// @nodoc - -class _$RecipeCreateStateImpl implements _RecipeCreateState { - const _$RecipeCreateStateImpl(); - - @override - String toString() { - return 'RecipeCreateState()'; - } - - @override - bool operator ==(Object other) { - return identical(this, other) || - (other.runtimeType == runtimeType && other is _$RecipeCreateStateImpl); - } - - @override - int get hashCode => runtimeType.hashCode; - - @override - @optionalTypeArgs - TResult when( - TResult Function() $default, { - required TResult Function() loading, - required TResult Function( - RecipeModel? recipe, - ImagePicker picker, - TextEditingController titleController, - TextEditingController productNameController, - TextEditingController ingredientController, - TextEditingController stepDescriptionController, - TextEditingController videoLinkController, - List stepControllers, - XFile? recipeImage, - List stepImages, - List ingredients, - List autocompleteList, - int numberOfSteps, - bool ingredientAsItems, - List types, - List cookingTimes, - List statuses, - SelectionPopupModel selectedType, - SelectionPopupModel selectedCookingTime, - SelectionPopupModel selectedStatus) - loaded, - required TResult Function(String errorMessage) error, - }) { - return $default(); - } - - @override - @optionalTypeArgs - TResult? whenOrNull( - TResult? Function()? $default, { - TResult? Function()? loading, - TResult? Function( - RecipeModel? recipe, - ImagePicker picker, - TextEditingController titleController, - TextEditingController productNameController, - TextEditingController ingredientController, - TextEditingController stepDescriptionController, - TextEditingController videoLinkController, - List stepControllers, - XFile? recipeImage, - List stepImages, - List ingredients, - List autocompleteList, - int numberOfSteps, - bool ingredientAsItems, - List types, - List cookingTimes, - List statuses, - SelectionPopupModel selectedType, - SelectionPopupModel selectedCookingTime, - SelectionPopupModel selectedStatus)? - loaded, - TResult? Function(String errorMessage)? error, - }) { - return $default?.call(); - } - - @override - @optionalTypeArgs - TResult maybeWhen( - TResult Function()? $default, { - TResult Function()? loading, - TResult Function( - RecipeModel? recipe, - ImagePicker picker, - TextEditingController titleController, - TextEditingController productNameController, - TextEditingController ingredientController, - TextEditingController stepDescriptionController, - TextEditingController videoLinkController, - List stepControllers, - XFile? recipeImage, - List stepImages, - List ingredients, - List autocompleteList, - int numberOfSteps, - bool ingredientAsItems, - List types, - List cookingTimes, - List statuses, - SelectionPopupModel selectedType, - SelectionPopupModel selectedCookingTime, - SelectionPopupModel selectedStatus)? - loaded, - TResult Function(String errorMessage)? error, - required TResult orElse(), - }) { - if ($default != null) { - return $default(); - } - return orElse(); - } - - @override - @optionalTypeArgs - TResult map( - TResult Function(_RecipeCreateState value) $default, { - required TResult Function(RecipeCreateStateLoading value) loading, - required TResult Function(RecipeCreateStateLoaded value) loaded, - required TResult Function(RecipeCreateStateError value) error, - }) { - return $default(this); - } - - @override - @optionalTypeArgs - TResult? mapOrNull( - TResult? Function(_RecipeCreateState value)? $default, { - TResult? Function(RecipeCreateStateLoading value)? loading, - TResult? Function(RecipeCreateStateLoaded value)? loaded, - TResult? Function(RecipeCreateStateError value)? error, - }) { - return $default?.call(this); - } - - @override - @optionalTypeArgs - TResult maybeMap( - TResult Function(_RecipeCreateState value)? $default, { - TResult Function(RecipeCreateStateLoading value)? loading, - TResult Function(RecipeCreateStateLoaded value)? loaded, - TResult Function(RecipeCreateStateError value)? error, - required TResult orElse(), - }) { - if ($default != null) { - return $default(this); - } - return orElse(); - } -} - -abstract class _RecipeCreateState implements RecipeCreateState { - const factory _RecipeCreateState() = _$RecipeCreateStateImpl; -} - -/// @nodoc -abstract class _$$RecipeCreateStateLoadingImplCopyWith<$Res> { - factory _$$RecipeCreateStateLoadingImplCopyWith( - _$RecipeCreateStateLoadingImpl value, - $Res Function(_$RecipeCreateStateLoadingImpl) then) = - __$$RecipeCreateStateLoadingImplCopyWithImpl<$Res>; -} - -/// @nodoc -class __$$RecipeCreateStateLoadingImplCopyWithImpl<$Res> - extends _$RecipeCreateStateCopyWithImpl<$Res, - _$RecipeCreateStateLoadingImpl> - implements _$$RecipeCreateStateLoadingImplCopyWith<$Res> { - __$$RecipeCreateStateLoadingImplCopyWithImpl( - _$RecipeCreateStateLoadingImpl _value, - $Res Function(_$RecipeCreateStateLoadingImpl) _then) - : super(_value, _then); - - /// Create a copy of RecipeCreateState - /// with the given fields replaced by the non-null parameter values. -} - -/// @nodoc - -class _$RecipeCreateStateLoadingImpl implements RecipeCreateStateLoading { - const _$RecipeCreateStateLoadingImpl(); - - @override - String toString() { - return 'RecipeCreateState.loading()'; - } - - @override - bool operator ==(Object other) { - return identical(this, other) || - (other.runtimeType == runtimeType && - other is _$RecipeCreateStateLoadingImpl); - } - - @override - int get hashCode => runtimeType.hashCode; - - @override - @optionalTypeArgs - TResult when( - TResult Function() $default, { - required TResult Function() loading, - required TResult Function( - RecipeModel? recipe, - ImagePicker picker, - TextEditingController titleController, - TextEditingController productNameController, - TextEditingController ingredientController, - TextEditingController stepDescriptionController, - TextEditingController videoLinkController, - List stepControllers, - XFile? recipeImage, - List stepImages, - List ingredients, - List autocompleteList, - int numberOfSteps, - bool ingredientAsItems, - List types, - List cookingTimes, - List statuses, - SelectionPopupModel selectedType, - SelectionPopupModel selectedCookingTime, - SelectionPopupModel selectedStatus) - loaded, - required TResult Function(String errorMessage) error, - }) { - return loading(); - } - - @override - @optionalTypeArgs - TResult? whenOrNull( - TResult? Function()? $default, { - TResult? Function()? loading, - TResult? Function( - RecipeModel? recipe, - ImagePicker picker, - TextEditingController titleController, - TextEditingController productNameController, - TextEditingController ingredientController, - TextEditingController stepDescriptionController, - TextEditingController videoLinkController, - List stepControllers, - XFile? recipeImage, - List stepImages, - List ingredients, - List autocompleteList, - int numberOfSteps, - bool ingredientAsItems, - List types, - List cookingTimes, - List statuses, - SelectionPopupModel selectedType, - SelectionPopupModel selectedCookingTime, - SelectionPopupModel selectedStatus)? - loaded, - TResult? Function(String errorMessage)? error, - }) { - return loading?.call(); - } - - @override - @optionalTypeArgs - TResult maybeWhen( - TResult Function()? $default, { - TResult Function()? loading, - TResult Function( - RecipeModel? recipe, - ImagePicker picker, - TextEditingController titleController, - TextEditingController productNameController, - TextEditingController ingredientController, - TextEditingController stepDescriptionController, - TextEditingController videoLinkController, - List stepControllers, - XFile? recipeImage, - List stepImages, - List ingredients, - List autocompleteList, - int numberOfSteps, - bool ingredientAsItems, - List types, - List cookingTimes, - List statuses, - SelectionPopupModel selectedType, - SelectionPopupModel selectedCookingTime, - SelectionPopupModel selectedStatus)? - loaded, - TResult Function(String errorMessage)? error, - required TResult orElse(), - }) { - if (loading != null) { - return loading(); - } - return orElse(); - } - - @override - @optionalTypeArgs - TResult map( - TResult Function(_RecipeCreateState value) $default, { - required TResult Function(RecipeCreateStateLoading value) loading, - required TResult Function(RecipeCreateStateLoaded value) loaded, - required TResult Function(RecipeCreateStateError value) error, - }) { - return loading(this); - } - - @override - @optionalTypeArgs - TResult? mapOrNull( - TResult? Function(_RecipeCreateState value)? $default, { - TResult? Function(RecipeCreateStateLoading value)? loading, - TResult? Function(RecipeCreateStateLoaded value)? loaded, - TResult? Function(RecipeCreateStateError value)? error, - }) { - return loading?.call(this); - } - - @override - @optionalTypeArgs - TResult maybeMap( - TResult Function(_RecipeCreateState value)? $default, { - TResult Function(RecipeCreateStateLoading value)? loading, - TResult Function(RecipeCreateStateLoaded value)? loaded, - TResult Function(RecipeCreateStateError value)? error, - required TResult orElse(), - }) { - if (loading != null) { - return loading(this); - } - return orElse(); - } -} - -abstract class RecipeCreateStateLoading implements RecipeCreateState { - const factory RecipeCreateStateLoading() = _$RecipeCreateStateLoadingImpl; -} - -/// @nodoc -abstract class _$$RecipeCreateStateLoadedImplCopyWith<$Res> { - factory _$$RecipeCreateStateLoadedImplCopyWith( - _$RecipeCreateStateLoadedImpl value, - $Res Function(_$RecipeCreateStateLoadedImpl) then) = - __$$RecipeCreateStateLoadedImplCopyWithImpl<$Res>; - @useResult - $Res call( - {RecipeModel? recipe, - ImagePicker picker, - TextEditingController titleController, - TextEditingController productNameController, - TextEditingController ingredientController, - TextEditingController stepDescriptionController, - TextEditingController videoLinkController, - List stepControllers, - XFile? recipeImage, - List stepImages, - List ingredients, - List autocompleteList, - int numberOfSteps, - bool ingredientAsItems, - List types, - List cookingTimes, - List statuses, - SelectionPopupModel selectedType, - SelectionPopupModel selectedCookingTime, - SelectionPopupModel selectedStatus}); - - $RecipeModelCopyWith<$Res>? get recipe; - $SelectionPopupModelCopyWith<$Res> get selectedType; - $SelectionPopupModelCopyWith<$Res> get selectedCookingTime; - $SelectionPopupModelCopyWith<$Res> get selectedStatus; -} - -/// @nodoc -class __$$RecipeCreateStateLoadedImplCopyWithImpl<$Res> - extends _$RecipeCreateStateCopyWithImpl<$Res, _$RecipeCreateStateLoadedImpl> - implements _$$RecipeCreateStateLoadedImplCopyWith<$Res> { - __$$RecipeCreateStateLoadedImplCopyWithImpl( - _$RecipeCreateStateLoadedImpl _value, - $Res Function(_$RecipeCreateStateLoadedImpl) _then) - : super(_value, _then); - - /// Create a copy of RecipeCreateState - /// with the given fields replaced by the non-null parameter values. - @pragma('vm:prefer-inline') - @override - $Res call({ - Object? recipe = freezed, - Object? picker = null, - Object? titleController = null, - Object? productNameController = null, - Object? ingredientController = null, - Object? stepDescriptionController = null, - Object? videoLinkController = null, - Object? stepControllers = null, - Object? recipeImage = freezed, - Object? stepImages = null, - Object? ingredients = null, - Object? autocompleteList = null, - Object? numberOfSteps = null, - Object? ingredientAsItems = null, - Object? types = null, - Object? cookingTimes = null, - Object? statuses = null, - Object? selectedType = null, - Object? selectedCookingTime = null, - Object? selectedStatus = null, - }) { - return _then(_$RecipeCreateStateLoadedImpl( - recipe: freezed == recipe - ? _value.recipe - : recipe // ignore: cast_nullable_to_non_nullable - as RecipeModel?, - picker: null == picker - ? _value.picker - : picker // ignore: cast_nullable_to_non_nullable - as ImagePicker, - titleController: null == titleController - ? _value.titleController - : titleController // ignore: cast_nullable_to_non_nullable - as TextEditingController, - productNameController: null == productNameController - ? _value.productNameController - : productNameController // ignore: cast_nullable_to_non_nullable - as TextEditingController, - ingredientController: null == ingredientController - ? _value.ingredientController - : ingredientController // ignore: cast_nullable_to_non_nullable - as TextEditingController, - stepDescriptionController: null == stepDescriptionController - ? _value.stepDescriptionController - : stepDescriptionController // ignore: cast_nullable_to_non_nullable - as TextEditingController, - videoLinkController: null == videoLinkController - ? _value.videoLinkController - : videoLinkController // ignore: cast_nullable_to_non_nullable - as TextEditingController, - stepControllers: null == stepControllers - ? _value._stepControllers - : stepControllers // ignore: cast_nullable_to_non_nullable - as List, - recipeImage: freezed == recipeImage - ? _value.recipeImage - : recipeImage // ignore: cast_nullable_to_non_nullable - as XFile?, - stepImages: null == stepImages - ? _value._stepImages - : stepImages // ignore: cast_nullable_to_non_nullable - as List, - ingredients: null == ingredients - ? _value._ingredients - : ingredients // ignore: cast_nullable_to_non_nullable - as List, - autocompleteList: null == autocompleteList - ? _value._autocompleteList - : autocompleteList // ignore: cast_nullable_to_non_nullable - as List, - numberOfSteps: null == numberOfSteps - ? _value.numberOfSteps - : numberOfSteps // ignore: cast_nullable_to_non_nullable - as int, - ingredientAsItems: null == ingredientAsItems - ? _value.ingredientAsItems - : ingredientAsItems // ignore: cast_nullable_to_non_nullable - as bool, - types: null == types - ? _value._types - : types // ignore: cast_nullable_to_non_nullable - as List, - cookingTimes: null == cookingTimes - ? _value._cookingTimes - : cookingTimes // ignore: cast_nullable_to_non_nullable - as List, - statuses: null == statuses - ? _value._statuses - : statuses // ignore: cast_nullable_to_non_nullable - as List, - selectedType: null == selectedType - ? _value.selectedType - : selectedType // ignore: cast_nullable_to_non_nullable - as SelectionPopupModel, - selectedCookingTime: null == selectedCookingTime - ? _value.selectedCookingTime - : selectedCookingTime // ignore: cast_nullable_to_non_nullable - as SelectionPopupModel, - selectedStatus: null == selectedStatus - ? _value.selectedStatus - : selectedStatus // ignore: cast_nullable_to_non_nullable - as SelectionPopupModel, - )); - } - - /// Create a copy of RecipeCreateState - /// with the given fields replaced by the non-null parameter values. - @override - @pragma('vm:prefer-inline') - $RecipeModelCopyWith<$Res>? get recipe { - if (_value.recipe == null) { - return null; - } - - return $RecipeModelCopyWith<$Res>(_value.recipe!, (value) { - return _then(_value.copyWith(recipe: value)); - }); - } - - /// Create a copy of RecipeCreateState - /// with the given fields replaced by the non-null parameter values. - @override - @pragma('vm:prefer-inline') - $SelectionPopupModelCopyWith<$Res> get selectedType { - return $SelectionPopupModelCopyWith<$Res>(_value.selectedType, (value) { - return _then(_value.copyWith(selectedType: value)); - }); - } - - /// Create a copy of RecipeCreateState - /// with the given fields replaced by the non-null parameter values. - @override - @pragma('vm:prefer-inline') - $SelectionPopupModelCopyWith<$Res> get selectedCookingTime { - return $SelectionPopupModelCopyWith<$Res>(_value.selectedCookingTime, - (value) { - return _then(_value.copyWith(selectedCookingTime: value)); - }); - } - - /// Create a copy of RecipeCreateState - /// with the given fields replaced by the non-null parameter values. - @override - @pragma('vm:prefer-inline') - $SelectionPopupModelCopyWith<$Res> get selectedStatus { - return $SelectionPopupModelCopyWith<$Res>(_value.selectedStatus, (value) { - return _then(_value.copyWith(selectedStatus: value)); - }); - } -} - -/// @nodoc - -class _$RecipeCreateStateLoadedImpl implements RecipeCreateStateLoaded { - const _$RecipeCreateStateLoadedImpl( - {this.recipe, - required this.picker, - required this.titleController, - required this.productNameController, - required this.ingredientController, - required this.stepDescriptionController, - required this.videoLinkController, - required final List stepControllers, - this.recipeImage, - required final List stepImages, - required final List ingredients, - required final List autocompleteList, - required this.numberOfSteps, - required this.ingredientAsItems, - required final List types, - required final List cookingTimes, - required final List statuses, - required this.selectedType, - required this.selectedCookingTime, - required this.selectedStatus}) - : _stepControllers = stepControllers, - _stepImages = stepImages, - _ingredients = ingredients, - _autocompleteList = autocompleteList, - _types = types, - _cookingTimes = cookingTimes, - _statuses = statuses; - - @override - final RecipeModel? recipe; - @override - final ImagePicker picker; - @override - final TextEditingController titleController; - @override - final TextEditingController productNameController; - @override - final TextEditingController ingredientController; - @override - final TextEditingController stepDescriptionController; - @override - final TextEditingController videoLinkController; - final List _stepControllers; - @override - List get stepControllers { - if (_stepControllers is EqualUnmodifiableListView) return _stepControllers; - // ignore: implicit_dynamic_type - return EqualUnmodifiableListView(_stepControllers); - } - - @override - final XFile? recipeImage; - final List _stepImages; - @override - List get stepImages { - if (_stepImages is EqualUnmodifiableListView) return _stepImages; - // ignore: implicit_dynamic_type - return EqualUnmodifiableListView(_stepImages); - } - - final List _ingredients; - @override - List get ingredients { - if (_ingredients is EqualUnmodifiableListView) return _ingredients; - // ignore: implicit_dynamic_type - return EqualUnmodifiableListView(_ingredients); - } - - final List _autocompleteList; - @override - List get autocompleteList { - if (_autocompleteList is EqualUnmodifiableListView) - return _autocompleteList; - // ignore: implicit_dynamic_type - return EqualUnmodifiableListView(_autocompleteList); - } - - @override - final int numberOfSteps; - @override - final bool ingredientAsItems; - final List _types; - @override - List get types { - if (_types is EqualUnmodifiableListView) return _types; - // ignore: implicit_dynamic_type - return EqualUnmodifiableListView(_types); - } - - final List _cookingTimes; - @override - List get cookingTimes { - if (_cookingTimes is EqualUnmodifiableListView) return _cookingTimes; - // ignore: implicit_dynamic_type - return EqualUnmodifiableListView(_cookingTimes); - } - - final List _statuses; - @override - List get statuses { - if (_statuses is EqualUnmodifiableListView) return _statuses; - // ignore: implicit_dynamic_type - return EqualUnmodifiableListView(_statuses); - } - - @override - final SelectionPopupModel selectedType; - @override - final SelectionPopupModel selectedCookingTime; - @override - final SelectionPopupModel selectedStatus; - - @override - String toString() { - return 'RecipeCreateState.loaded(recipe: $recipe, picker: $picker, titleController: $titleController, productNameController: $productNameController, ingredientController: $ingredientController, stepDescriptionController: $stepDescriptionController, videoLinkController: $videoLinkController, stepControllers: $stepControllers, recipeImage: $recipeImage, stepImages: $stepImages, ingredients: $ingredients, autocompleteList: $autocompleteList, numberOfSteps: $numberOfSteps, ingredientAsItems: $ingredientAsItems, types: $types, cookingTimes: $cookingTimes, statuses: $statuses, selectedType: $selectedType, selectedCookingTime: $selectedCookingTime, selectedStatus: $selectedStatus)'; - } - - @override - bool operator ==(Object other) { - return identical(this, other) || - (other.runtimeType == runtimeType && - other is _$RecipeCreateStateLoadedImpl && - (identical(other.recipe, recipe) || other.recipe == recipe) && - (identical(other.picker, picker) || other.picker == picker) && - (identical(other.titleController, titleController) || - other.titleController == titleController) && - (identical(other.productNameController, productNameController) || - other.productNameController == productNameController) && - (identical(other.ingredientController, ingredientController) || - other.ingredientController == ingredientController) && - (identical(other.stepDescriptionController, - stepDescriptionController) || - other.stepDescriptionController == stepDescriptionController) && - (identical(other.videoLinkController, videoLinkController) || - other.videoLinkController == videoLinkController) && - const DeepCollectionEquality() - .equals(other._stepControllers, _stepControllers) && - (identical(other.recipeImage, recipeImage) || - other.recipeImage == recipeImage) && - const DeepCollectionEquality() - .equals(other._stepImages, _stepImages) && - const DeepCollectionEquality() - .equals(other._ingredients, _ingredients) && - const DeepCollectionEquality() - .equals(other._autocompleteList, _autocompleteList) && - (identical(other.numberOfSteps, numberOfSteps) || - other.numberOfSteps == numberOfSteps) && - (identical(other.ingredientAsItems, ingredientAsItems) || - other.ingredientAsItems == ingredientAsItems) && - const DeepCollectionEquality().equals(other._types, _types) && - const DeepCollectionEquality() - .equals(other._cookingTimes, _cookingTimes) && - const DeepCollectionEquality().equals(other._statuses, _statuses) && - (identical(other.selectedType, selectedType) || - other.selectedType == selectedType) && - (identical(other.selectedCookingTime, selectedCookingTime) || - other.selectedCookingTime == selectedCookingTime) && - (identical(other.selectedStatus, selectedStatus) || - other.selectedStatus == selectedStatus)); - } - - @override - int get hashCode => Object.hashAll([ - runtimeType, - recipe, - picker, - titleController, - productNameController, - ingredientController, - stepDescriptionController, - videoLinkController, - const DeepCollectionEquality().hash(_stepControllers), - recipeImage, - const DeepCollectionEquality().hash(_stepImages), - const DeepCollectionEquality().hash(_ingredients), - const DeepCollectionEquality().hash(_autocompleteList), - numberOfSteps, - ingredientAsItems, - const DeepCollectionEquality().hash(_types), - const DeepCollectionEquality().hash(_cookingTimes), - const DeepCollectionEquality().hash(_statuses), - selectedType, - selectedCookingTime, - selectedStatus - ]); - - /// Create a copy of RecipeCreateState - /// with the given fields replaced by the non-null parameter values. - @JsonKey(includeFromJson: false, includeToJson: false) - @override - @pragma('vm:prefer-inline') - _$$RecipeCreateStateLoadedImplCopyWith<_$RecipeCreateStateLoadedImpl> - get copyWith => __$$RecipeCreateStateLoadedImplCopyWithImpl< - _$RecipeCreateStateLoadedImpl>(this, _$identity); - - @override - @optionalTypeArgs - TResult when( - TResult Function() $default, { - required TResult Function() loading, - required TResult Function( - RecipeModel? recipe, - ImagePicker picker, - TextEditingController titleController, - TextEditingController productNameController, - TextEditingController ingredientController, - TextEditingController stepDescriptionController, - TextEditingController videoLinkController, - List stepControllers, - XFile? recipeImage, - List stepImages, - List ingredients, - List autocompleteList, - int numberOfSteps, - bool ingredientAsItems, - List types, - List cookingTimes, - List statuses, - SelectionPopupModel selectedType, - SelectionPopupModel selectedCookingTime, - SelectionPopupModel selectedStatus) - loaded, - required TResult Function(String errorMessage) error, - }) { - return loaded( - recipe, - picker, - titleController, - productNameController, - ingredientController, - stepDescriptionController, - videoLinkController, - stepControllers, - recipeImage, - stepImages, - ingredients, - autocompleteList, - numberOfSteps, - ingredientAsItems, - types, - cookingTimes, - statuses, - selectedType, - selectedCookingTime, - selectedStatus); - } - - @override - @optionalTypeArgs - TResult? whenOrNull( - TResult? Function()? $default, { - TResult? Function()? loading, - TResult? Function( - RecipeModel? recipe, - ImagePicker picker, - TextEditingController titleController, - TextEditingController productNameController, - TextEditingController ingredientController, - TextEditingController stepDescriptionController, - TextEditingController videoLinkController, - List stepControllers, - XFile? recipeImage, - List stepImages, - List ingredients, - List autocompleteList, - int numberOfSteps, - bool ingredientAsItems, - List types, - List cookingTimes, - List statuses, - SelectionPopupModel selectedType, - SelectionPopupModel selectedCookingTime, - SelectionPopupModel selectedStatus)? - loaded, - TResult? Function(String errorMessage)? error, - }) { - return loaded?.call( - recipe, - picker, - titleController, - productNameController, - ingredientController, - stepDescriptionController, - videoLinkController, - stepControllers, - recipeImage, - stepImages, - ingredients, - autocompleteList, - numberOfSteps, - ingredientAsItems, - types, - cookingTimes, - statuses, - selectedType, - selectedCookingTime, - selectedStatus); - } - - @override - @optionalTypeArgs - TResult maybeWhen( - TResult Function()? $default, { - TResult Function()? loading, - TResult Function( - RecipeModel? recipe, - ImagePicker picker, - TextEditingController titleController, - TextEditingController productNameController, - TextEditingController ingredientController, - TextEditingController stepDescriptionController, - TextEditingController videoLinkController, - List stepControllers, - XFile? recipeImage, - List stepImages, - List ingredients, - List autocompleteList, - int numberOfSteps, - bool ingredientAsItems, - List types, - List cookingTimes, - List statuses, - SelectionPopupModel selectedType, - SelectionPopupModel selectedCookingTime, - SelectionPopupModel selectedStatus)? - loaded, - TResult Function(String errorMessage)? error, - required TResult orElse(), - }) { - if (loaded != null) { - return loaded( - recipe, - picker, - titleController, - productNameController, - ingredientController, - stepDescriptionController, - videoLinkController, - stepControllers, - recipeImage, - stepImages, - ingredients, - autocompleteList, - numberOfSteps, - ingredientAsItems, - types, - cookingTimes, - statuses, - selectedType, - selectedCookingTime, - selectedStatus); - } - return orElse(); - } - - @override - @optionalTypeArgs - TResult map( - TResult Function(_RecipeCreateState value) $default, { - required TResult Function(RecipeCreateStateLoading value) loading, - required TResult Function(RecipeCreateStateLoaded value) loaded, - required TResult Function(RecipeCreateStateError value) error, - }) { - return loaded(this); - } - - @override - @optionalTypeArgs - TResult? mapOrNull( - TResult? Function(_RecipeCreateState value)? $default, { - TResult? Function(RecipeCreateStateLoading value)? loading, - TResult? Function(RecipeCreateStateLoaded value)? loaded, - TResult? Function(RecipeCreateStateError value)? error, - }) { - return loaded?.call(this); - } - - @override - @optionalTypeArgs - TResult maybeMap( - TResult Function(_RecipeCreateState value)? $default, { - TResult Function(RecipeCreateStateLoading value)? loading, - TResult Function(RecipeCreateStateLoaded value)? loaded, - TResult Function(RecipeCreateStateError value)? error, - required TResult orElse(), - }) { - if (loaded != null) { - return loaded(this); - } - return orElse(); - } -} - -abstract class RecipeCreateStateLoaded implements RecipeCreateState { - const factory RecipeCreateStateLoaded( - {final RecipeModel? recipe, - required final ImagePicker picker, - required final TextEditingController titleController, - required final TextEditingController productNameController, - required final TextEditingController ingredientController, - required final TextEditingController stepDescriptionController, - required final TextEditingController videoLinkController, - required final List stepControllers, - final XFile? recipeImage, - required final List stepImages, - required final List ingredients, - required final List autocompleteList, - required final int numberOfSteps, - required final bool ingredientAsItems, - required final List types, - required final List cookingTimes, - required final List statuses, - required final SelectionPopupModel selectedType, - required final SelectionPopupModel selectedCookingTime, - required final SelectionPopupModel selectedStatus}) = - _$RecipeCreateStateLoadedImpl; - - RecipeModel? get recipe; - ImagePicker get picker; - TextEditingController get titleController; - TextEditingController get productNameController; - TextEditingController get ingredientController; - TextEditingController get stepDescriptionController; - TextEditingController get videoLinkController; - List get stepControllers; - XFile? get recipeImage; - List get stepImages; - List get ingredients; - List get autocompleteList; - int get numberOfSteps; - bool get ingredientAsItems; - List get types; - List get cookingTimes; - List get statuses; - SelectionPopupModel get selectedType; - SelectionPopupModel get selectedCookingTime; - SelectionPopupModel get selectedStatus; - - /// Create a copy of RecipeCreateState - /// with the given fields replaced by the non-null parameter values. - @JsonKey(includeFromJson: false, includeToJson: false) - _$$RecipeCreateStateLoadedImplCopyWith<_$RecipeCreateStateLoadedImpl> - get copyWith => throw _privateConstructorUsedError; -} - -/// @nodoc -abstract class _$$RecipeCreateStateErrorImplCopyWith<$Res> { - factory _$$RecipeCreateStateErrorImplCopyWith( - _$RecipeCreateStateErrorImpl value, - $Res Function(_$RecipeCreateStateErrorImpl) then) = - __$$RecipeCreateStateErrorImplCopyWithImpl<$Res>; - @useResult - $Res call({String errorMessage}); -} - -/// @nodoc -class __$$RecipeCreateStateErrorImplCopyWithImpl<$Res> - extends _$RecipeCreateStateCopyWithImpl<$Res, _$RecipeCreateStateErrorImpl> - implements _$$RecipeCreateStateErrorImplCopyWith<$Res> { - __$$RecipeCreateStateErrorImplCopyWithImpl( - _$RecipeCreateStateErrorImpl _value, - $Res Function(_$RecipeCreateStateErrorImpl) _then) - : super(_value, _then); - - /// Create a copy of RecipeCreateState - /// with the given fields replaced by the non-null parameter values. - @pragma('vm:prefer-inline') - @override - $Res call({ - Object? errorMessage = null, - }) { - return _then(_$RecipeCreateStateErrorImpl( - errorMessage: null == errorMessage - ? _value.errorMessage - : errorMessage // ignore: cast_nullable_to_non_nullable - as String, - )); - } -} - -/// @nodoc - -class _$RecipeCreateStateErrorImpl implements RecipeCreateStateError { - const _$RecipeCreateStateErrorImpl({required this.errorMessage}); - - @override - final String errorMessage; - - @override - String toString() { - return 'RecipeCreateState.error(errorMessage: $errorMessage)'; - } - - @override - bool operator ==(Object other) { - return identical(this, other) || - (other.runtimeType == runtimeType && - other is _$RecipeCreateStateErrorImpl && - (identical(other.errorMessage, errorMessage) || - other.errorMessage == errorMessage)); - } - - @override - int get hashCode => Object.hash(runtimeType, errorMessage); - - /// Create a copy of RecipeCreateState - /// with the given fields replaced by the non-null parameter values. - @JsonKey(includeFromJson: false, includeToJson: false) - @override - @pragma('vm:prefer-inline') - _$$RecipeCreateStateErrorImplCopyWith<_$RecipeCreateStateErrorImpl> - get copyWith => __$$RecipeCreateStateErrorImplCopyWithImpl< - _$RecipeCreateStateErrorImpl>(this, _$identity); - - @override - @optionalTypeArgs - TResult when( - TResult Function() $default, { - required TResult Function() loading, - required TResult Function( - RecipeModel? recipe, - ImagePicker picker, - TextEditingController titleController, - TextEditingController productNameController, - TextEditingController ingredientController, - TextEditingController stepDescriptionController, - TextEditingController videoLinkController, - List stepControllers, - XFile? recipeImage, - List stepImages, - List ingredients, - List autocompleteList, - int numberOfSteps, - bool ingredientAsItems, - List types, - List cookingTimes, - List statuses, - SelectionPopupModel selectedType, - SelectionPopupModel selectedCookingTime, - SelectionPopupModel selectedStatus) - loaded, - required TResult Function(String errorMessage) error, - }) { - return error(errorMessage); - } - - @override - @optionalTypeArgs - TResult? whenOrNull( - TResult? Function()? $default, { - TResult? Function()? loading, - TResult? Function( - RecipeModel? recipe, - ImagePicker picker, - TextEditingController titleController, - TextEditingController productNameController, - TextEditingController ingredientController, - TextEditingController stepDescriptionController, - TextEditingController videoLinkController, - List stepControllers, - XFile? recipeImage, - List stepImages, - List ingredients, - List autocompleteList, - int numberOfSteps, - bool ingredientAsItems, - List types, - List cookingTimes, - List statuses, - SelectionPopupModel selectedType, - SelectionPopupModel selectedCookingTime, - SelectionPopupModel selectedStatus)? - loaded, - TResult? Function(String errorMessage)? error, - }) { - return error?.call(errorMessage); - } - - @override - @optionalTypeArgs - TResult maybeWhen( - TResult Function()? $default, { - TResult Function()? loading, - TResult Function( - RecipeModel? recipe, - ImagePicker picker, - TextEditingController titleController, - TextEditingController productNameController, - TextEditingController ingredientController, - TextEditingController stepDescriptionController, - TextEditingController videoLinkController, - List stepControllers, - XFile? recipeImage, - List stepImages, - List ingredients, - List autocompleteList, - int numberOfSteps, - bool ingredientAsItems, - List types, - List cookingTimes, - List statuses, - SelectionPopupModel selectedType, - SelectionPopupModel selectedCookingTime, - SelectionPopupModel selectedStatus)? - loaded, - TResult Function(String errorMessage)? error, - required TResult orElse(), - }) { - if (error != null) { - return error(errorMessage); - } - return orElse(); - } - - @override - @optionalTypeArgs - TResult map( - TResult Function(_RecipeCreateState value) $default, { - required TResult Function(RecipeCreateStateLoading value) loading, - required TResult Function(RecipeCreateStateLoaded value) loaded, - required TResult Function(RecipeCreateStateError value) error, - }) { - return error(this); - } - - @override - @optionalTypeArgs - TResult? mapOrNull( - TResult? Function(_RecipeCreateState value)? $default, { - TResult? Function(RecipeCreateStateLoading value)? loading, - TResult? Function(RecipeCreateStateLoaded value)? loaded, - TResult? Function(RecipeCreateStateError value)? error, - }) { - return error?.call(this); - } - - @override - @optionalTypeArgs - TResult maybeMap( - TResult Function(_RecipeCreateState value)? $default, { - TResult Function(RecipeCreateStateLoading value)? loading, - TResult Function(RecipeCreateStateLoaded value)? loaded, - TResult Function(RecipeCreateStateError value)? error, - required TResult orElse(), - }) { - if (error != null) { - return error(this); - } - return orElse(); - } -} - -abstract class RecipeCreateStateError implements RecipeCreateState { - const factory RecipeCreateStateError({required final String errorMessage}) = - _$RecipeCreateStateErrorImpl; - - String get errorMessage; - - /// Create a copy of RecipeCreateState - /// with the given fields replaced by the non-null parameter values. - @JsonKey(includeFromJson: false, includeToJson: false) - _$$RecipeCreateStateErrorImplCopyWith<_$RecipeCreateStateErrorImpl> - get copyWith => throw _privateConstructorUsedError; -} diff --git a/lib/features/recipes/presentation/pages/recipe_create_screen.dart b/lib/features/recipes/presentation/pages/recipe_create_screen.dart index bc95e6c3..efeace26 100644 --- a/lib/features/recipes/presentation/pages/recipe_create_screen.dart +++ b/lib/features/recipes/presentation/pages/recipe_create_screen.dart @@ -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 createState() => _RecipeCreateScreenState(); +} + +class _RecipeCreateScreenState extends State { + @override Widget build(BuildContext context) { - return BlocProvider( - create: (context) => sl()..loadRecipeCreate(), - child: BlocBuilder( - 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 _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( + create: (BuildContext context) => sl(), + 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().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( - 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 onSelected, - Iterable 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: [FilteringTextInputFormatter.digitsOnly], - ), - SizedBox(height: 8), - Row(children: [ - Text('гр'), - Switch( - value: state.ingredientAsItems, - onChanged: (bool value) => context.read().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().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().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().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().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().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 _options) { - return (TextEditingValue textEditingValue) { - return _options.where((String option) { - return true; - }); - }; - } - - selectProduct(AutocompleteOnSelected 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().inputProduct(value); - } - }; - } - - changeType(BuildContext context) { - return (value) { - context.read().changeType(value); - }; - } - - changeStatus(BuildContext context) { - return (value) { - context.read().changeStatus(value); - }; - } - - changeCookingTime(BuildContext context) { - return (value) { - context.read().changeCookingTime(value); - }; - } - - RecipeModel createRecipeFromState(RecipeCreateStateLoaded state) { - List ingredients = state.ingredients; - - List 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; - } } diff --git a/lib/features/recipes/presentation/pages/recipe_edit_screen.dart b/lib/features/recipes/presentation/pages/recipe_edit_screen.dart deleted file mode 100644 index dffccf3f..00000000 --- a/lib/features/recipes/presentation/pages/recipe_edit_screen.dart +++ /dev/null @@ -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().loadRecipeById(recipeID); - } - return BlocBuilder( - 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 _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().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( - 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 onSelected, - Iterable 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: [FilteringTextInputFormatter.digitsOnly], - ), - SizedBox(height: 8), - Row(children: [ - Text('гр'), - Switch( - value: state.ingredientAsItems, - onChanged: (bool value) => context.read().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().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().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().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().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().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 _options) { - return (TextEditingValue textEditingValue) { - return _options.where((String option) { - return true; - }); - }; - } - - selectProduct(AutocompleteOnSelected 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().inputProduct(value); - } - }; - } - - changeType(BuildContext context) { - return (value) { - context.read().changeType(value); - }; - } - - changeStatus(BuildContext context) { - return (value) { - context.read().changeStatus(value); - }; - } - - changeCookingTime(BuildContext context) { - return (value) { - context.read().changeCookingTime(value); - }; - } - - RecipeModel createRecipeFromState(RecipeCreateStateLoaded state) { - List ingredients = state.ingredients; - - List 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; - } -} diff --git a/lib/features/recipes/presentation/widgets/image_widget.dart b/lib/features/recipes/presentation/widgets/image_widget.dart new file mode 100644 index 00000000..2521ff69 --- /dev/null +++ b/lib/features/recipes/presentation/widgets/image_widget.dart @@ -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 createState() => _ImageWidgetState(); +} + +class _ImageWidgetState extends State { + XFile? image; + late final CreateRecipeCubit createRecipeCubit; + + @override + void initState() { + super.initState(); + createRecipeCubit = context.read(); + } + + @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( + 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('Отмена'), + ), + ), + ); + } +} diff --git a/lib/features/recipes/presentation/widgets/info_about_recipe_widget.dart b/lib/features/recipes/presentation/widgets/info_about_recipe_widget.dart new file mode 100644 index 00000000..473cc04a --- /dev/null +++ b/lib/features/recipes/presentation/widgets/info_about_recipe_widget.dart @@ -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 createState() => _InfoAboutRecipeWidgetState(); +} + +class _InfoAboutRecipeWidgetState extends State { + final List 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(); + } + + @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); + }, + ), + ], + ), + ), + ); + } +} diff --git a/lib/features/recipes/presentation/widgets/ingredients_widget.dart b/lib/features/recipes/presentation/widgets/ingredients_widget.dart new file mode 100644 index 00000000..e1ac4748 --- /dev/null +++ b/lib/features/recipes/presentation/widgets/ingredients_widget.dart @@ -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 createState() => _IngredientsWidgetState(); +} + +class _IngredientsWidgetState extends State { + late final CreateRecipeCubit createRecipeCubit; + final nameController = TextEditingController(); + final gramsController = TextEditingController(); + final pcsController = TextEditingController(); + + @override + void initState() { + super.initState(); + createRecipeCubit = context.read(); + } + + @override + Widget build(BuildContext context) { + return BlocBuilder( + 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: [ + 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, + ), + ), + ], + ), + ), + ); + }, + ); + } +} diff --git a/lib/features/recipes/presentation/widgets/portion_widget.dart b/lib/features/recipes/presentation/widgets/portion_widget.dart new file mode 100644 index 00000000..875183ff --- /dev/null +++ b/lib/features/recipes/presentation/widgets/portion_widget.dart @@ -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 createState() => _PortionWidgetState(); +} + +class _PortionWidgetState extends State { + late final CreateRecipeCubit createRecipeCubit; + + @override + void initState() { + super.initState(); + createRecipeCubit = context.read(); + } + + @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)); + }, + ), + ], + ), + ), + ], + ), + ], + ); + } +} diff --git a/lib/features/recipes/presentation/widgets/step_image_widget.dart b/lib/features/recipes/presentation/widgets/step_image_widget.dart new file mode 100644 index 00000000..ad4c9549 --- /dev/null +++ b/lib/features/recipes/presentation/widgets/step_image_widget.dart @@ -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 createState() => _StepImageWidgetState(); +} + +class _StepImageWidgetState extends State { + XFile? image; + late final CreateRecipeCubit createRecipeCubit; + + @override + void initState() { + super.initState(); + createRecipeCubit = context.read(); + } + + @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( + 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('Отмена'), + ), + ), + ); + } +} diff --git a/lib/features/recipes/presentation/widgets/steps_widget.dart b/lib/features/recipes/presentation/widgets/steps_widget.dart new file mode 100644 index 00000000..6feb9aa6 --- /dev/null +++ b/lib/features/recipes/presentation/widgets/steps_widget.dart @@ -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 createState() => _StepsWidgetState(); +} + +class _StepsWidgetState extends State { + late final CreateRecipeCubit createRecipeCubit; + + @override + void initState() { + super.initState(); + createRecipeCubit = context.read(); + } + + @override + Widget build(BuildContext context) { + return BlocBuilder( + 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().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().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, + ), + ), + ), + ), + ), + ], + ), + ), + ); + }, + ); + } +} diff --git a/lib/features/recipes/presentation/widgets/tags_widget.dart b/lib/features/recipes/presentation/widgets/tags_widget.dart new file mode 100644 index 00000000..53ef22bf --- /dev/null +++ b/lib/features/recipes/presentation/widgets/tags_widget.dart @@ -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 createState() => _TagsWidgetState(); +} + +class _TagsWidgetState extends State { + final tagController = TextEditingController(); + final List 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, + ), + ], + ), + ), + ), + ], + ], + ), + ], + ); + } +} diff --git a/lib/features/recipes/presentation/widgets/time_of_cook_widget.dart b/lib/features/recipes/presentation/widgets/time_of_cook_widget.dart new file mode 100644 index 00000000..f329f791 --- /dev/null +++ b/lib/features/recipes/presentation/widgets/time_of_cook_widget.dart @@ -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 createState() => _TimeOfCookWidgetState(); +} + +class _TimeOfCookWidgetState extends State { + late final CreateRecipeCubit createRecipeCubit; + + @override + void initState() { + super.initState(); + createRecipeCubit = context.read(); + } + + @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')}'); + } + }, + ), + ), + ], + ), + ], + ); + } +} diff --git a/lib/features/registration/models/user_registration_model.dart b/lib/features/registration/models/user_registration_model.dart index 64b9c9e5..9b56becc 100644 --- a/lib/features/registration/models/user_registration_model.dart +++ b/lib/features/registration/models/user_registration_model.dart @@ -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, diff --git a/lib/features/registration/models/user_registration_model.freezed.dart b/lib/features/registration/models/user_registration_model.freezed.dart index fd835a26..aed86670 100644 --- a/lib/features/registration/models/user_registration_model.freezed.dart +++ b/lib/features/registration/models/user_registration_model.freezed.dart @@ -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 diff --git a/lib/features/registration/models/user_registration_model.g.dart b/lib/features/registration/models/user_registration_model.g.dart index 9500e915..076923a5 100644 --- a/lib/features/registration/models/user_registration_model.g.dart +++ b/lib/features/registration/models/user_registration_model.g.dart @@ -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 _$$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, diff --git a/lib/features/registration/presentation/bloc/registration_cubit/registration_cubit.dart b/lib/features/registration/presentation/bloc/registration_cubit/registration_cubit.dart index 78af9980..6fa7cbd6 100644 --- a/lib/features/registration/presentation/bloc/registration_cubit/registration_cubit.dart +++ b/lib/features/registration/presentation/bloc/registration_cubit/registration_cubit.dart @@ -24,7 +24,7 @@ class RegistrationCubit extends Cubit { gender: '', location: '', responseBody: '', - lang: '', + lang: 'Русский', weight: 0, height: 0, chestCircumference: 0, @@ -40,6 +40,7 @@ class RegistrationCubit extends Cubit { favoriteFoods: '', unfavoriteFoods: '', additionalInfo: '', + phone: '', ), ), ); @@ -48,7 +49,11 @@ class RegistrationCubit extends Cubit { Future 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) { diff --git a/lib/features/registration/presentation/pages/additional_information_screen.dart b/lib/features/registration/presentation/pages/additional_information_screen.dart index dc8e9b3d..71a47550 100644 --- a/lib/features/registration/presentation/pages/additional_information_screen.dart +++ b/lib/features/registration/presentation/pages/additional_information_screen.dart @@ -33,7 +33,7 @@ class _AdditionalInformationScreenState extends State { child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - Text(s.allergies_presence), + Text(s.allergies_presence + ' (не обязательно)'), SizedBox(height: 4), CustomTextFormField( controller: enterInformationController, diff --git a/lib/features/registration/presentation/pages/blood_pressure_screen.dart b/lib/features/registration/presentation/pages/blood_pressure_screen.dart index cc8af83c..e3d1f688 100644 --- a/lib/features/registration/presentation/pages/blood_pressure_screen.dart +++ b/lib/features/registration/presentation/pages/blood_pressure_screen.dart @@ -58,7 +58,7 @@ class _BloodPressureScreenState extends State { 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 { }, ), 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 { }, ), 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 { }, ), SizedBox(height: 16), - Text(s.do_you_have_hypertension_tendency), + Text(s.do_you_have_hypertension_tendency + ' (не обязательно)'), SizedBox(height: 4), CustomDropDown( icon: Container( diff --git a/lib/features/registration/presentation/pages/body_circumferences_screen.dart b/lib/features/registration/presentation/pages/body_circumferences_screen.dart index 728ef182..e14c62ef 100644 --- a/lib/features/registration/presentation/pages/body_circumferences_screen.dart +++ b/lib/features/registration/presentation/pages/body_circumferences_screen.dart @@ -39,7 +39,7 @@ class _BodyCircumferencesScreenState extends State { 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 { }, ), 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 { }, ), SizedBox(height: 16), - Text(s.hip_circumference), + Text(s.hip_circumference + ' (не обязательно)'), SizedBox(height: 4), CustomTextFormField( inputFormatters: [digitsOnly], diff --git a/lib/features/registration/presentation/pages/city_of_living_screen.dart b/lib/features/registration/presentation/pages/city_of_living_screen.dart index 177c036f..8e049fd0 100644 --- a/lib/features/registration/presentation/pages/city_of_living_screen.dart +++ b/lib/features/registration/presentation/pages/city_of_living_screen.dart @@ -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 { if (widget.sheet.location.isNotEmpty) { cityController.text = widget.sheet.location; } + tryGetLocation(); + } + + tryGetLocation() async { + final LocationData? locationData = await getLocationData(); + if (locationData != null) { + final List locations = await context + .read() + .getLocationsByCoordinates(locationData.latitude.toString(), locationData.longitude.toString()); + final registrationCubit = context.read(); + registrationCubit.updateSheet(registrationCubit.state.sheet.copyWith(location: locations.first)); + setState(() { + cityController.text = locations.first; + suggestionsController.suggestions = locations; + }); + } } Future getLocationData() async { @@ -76,19 +91,21 @@ class _CityOfLivingScreenState extends State { 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 { }, ), ), - SizedBox(width: 8), - InkWell( - borderRadius: BorderRadius.circular(8), - onTap: !isLoading - ? () async { - setState(() { - isLoading = true; - }); - final LocationData? locationData = await getLocationData(); - if (locationData != null) { - final List locations = await context - .read() - .getLocationsByCoordinates(locationData.latitude.toString(), locationData.longitude.toString()); - final registrationCubit = context.read(); - 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(), - ), - ), ], ), ], diff --git a/lib/features/registration/presentation/pages/credentials_screen.dart b/lib/features/registration/presentation/pages/credentials_screen.dart index ee9ea21d..06b9dc17 100644 --- a/lib/features/registration/presentation/pages/credentials_screen.dart +++ b/lib/features/registration/presentation/pages/credentials_screen.dart @@ -20,7 +20,7 @@ class _CredentialsScreenState extends State { final passwordController = TextEditingController(); final passwordConfirmationController = TextEditingController(); - + final emailFocusNode = FocusNode(); bool hidePassword = true; @override @@ -29,6 +29,7 @@ class _CredentialsScreenState extends State { 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 { 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(); diff --git a/lib/features/registration/presentation/pages/date_of_birth_screen.dart b/lib/features/registration/presentation/pages/date_of_birth_screen.dart index 6369c2c0..b8a9aad4 100644 --- a/lib/features/registration/presentation/pages/date_of_birth_screen.dart +++ b/lib/features/registration/presentation/pages/date_of_birth_screen.dart @@ -85,7 +85,7 @@ class _DateOfBirthScreenState extends State { showCupertinoModalPopup( 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 { 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, + ), + ), + ), + ), + ), + ), + ), + ], + ), ), ), ); diff --git a/lib/features/registration/presentation/pages/favorite_foods_screen.dart b/lib/features/registration/presentation/pages/favorite_foods_screen.dart index a7176ce5..6b662609 100644 --- a/lib/features/registration/presentation/pages/favorite_foods_screen.dart +++ b/lib/features/registration/presentation/pages/favorite_foods_screen.dart @@ -35,7 +35,7 @@ class _FavoriteFoodsScreenState extends State { 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 { }, ), SizedBox(height: 16), - Text(s.unfavorite_foods), + Text(s.unfavorite_foods + ' (не обязательно)'), SizedBox(height: 4), CustomTextFormField( controller: unFavoriteFoodsController, diff --git a/lib/features/registration/presentation/pages/favorite_sports_screen.dart b/lib/features/registration/presentation/pages/favorite_sports_screen.dart index 88af7f5a..d78311ec 100644 --- a/lib/features/registration/presentation/pages/favorite_sports_screen.dart +++ b/lib/features/registration/presentation/pages/favorite_sports_screen.dart @@ -33,7 +33,7 @@ class _FavoriteSportsScreenState extends State { child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - Text(s.your_favorite_sports), + Text(s.your_favorite_sports + ' (не обязательно)'), SizedBox(height: 4), CustomTextFormField( controller: enterInformationController, diff --git a/lib/features/registration/presentation/pages/medicine_intake_screen.dart b/lib/features/registration/presentation/pages/medicine_intake_screen.dart index f7069a49..214e30c7 100644 --- a/lib/features/registration/presentation/pages/medicine_intake_screen.dart +++ b/lib/features/registration/presentation/pages/medicine_intake_screen.dart @@ -33,7 +33,7 @@ class _MedicineIntakeScreenState extends State { child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - Text(s.do_you_take_pills), + Text(s.do_you_take_pills + ' (не обязательно)'), SizedBox(height: 4), CustomTextFormField( controller: enterInformationController, diff --git a/lib/features/registration/presentation/pages/phone_screen.dart b/lib/features/registration/presentation/pages/phone_screen.dart new file mode 100644 index 00000000..99942cc1 --- /dev/null +++ b/lib/features/registration/presentation/pages/phone_screen.dart @@ -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 createState() => _PhoneScreenState(); +} + +class _PhoneScreenState extends State { + 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.updateSheet( + registrationCubit.state.sheet.copyWith( + phone: newValue, + ), + ); + }, + ), + ], + ), + ); + } +} diff --git a/lib/features/registration/presentation/pages/registration_screen.dart b/lib/features/registration/presentation/pages/registration_screen.dart index 9c44163e..7331b852 100644 --- a/lib/features/registration/presentation/pages/registration_screen.dart +++ b/lib/features/registration/presentation/pages/registration_screen.dart @@ -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 { }); }, 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 { Future 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 errors = validateCredentialsFields( state.sheet.email, diff --git a/lib/generated/intl/messages_all.dart b/lib/generated/intl/messages_all.dart index 203415cc..abae25a5 100644 --- a/lib/generated/intl/messages_all.dart +++ b/lib/generated/intl/messages_all.dart @@ -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 LibraryLoader(); Map _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; } diff --git a/lib/generated/intl/messages_ru.dart b/lib/generated/intl/messages_ru.dart new file mode 100644 index 00000000..e1e70e28 --- /dev/null +++ b/lib/generated/intl/messages_ru.dart @@ -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 args); + +class MessageLookup extends MessageLookupByLibrary { + String get localeName => 'ru'; + + final messages = _notInlinedMessages(_notInlinedMessages); + static Map _notInlinedMessages(_) => { + "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("Ваш вес:"), + }; +} diff --git a/lib/generated/l10n.dart b/lib/generated/l10n.dart index b8f0195c..52654a58 100644 --- a/lib/generated/l10n.dart +++ b/lib/generated/l10n.dart @@ -2177,6 +2177,7 @@ class AppLocalizationDelegate extends LocalizationsDelegate { List get supportedLocales { return const [ Locale.fromSubtags(languageCode: 'en'), + Locale.fromSubtags(languageCode: 'ru'), ]; } diff --git a/lib/l10n/intl_en.arb b/lib/l10n/intl_en.arb index 16d0d7ee..1c1d0b2e 100644 --- a/lib/l10n/intl_en.arb +++ b/lib/l10n/intl_en.arb @@ -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": "Галерея", diff --git a/lib/l10n/intl_ru.arb b/lib/l10n/intl_ru.arb new file mode 100644 index 00000000..b5f2d8a3 --- /dev/null +++ b/lib/l10n/intl_ru.arb @@ -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● Физическая активность;" +} diff --git a/lib/main.dart b/lib/main.dart index edc0a152..8672683a 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -45,7 +45,9 @@ class MyApp extends StatelessWidget { GlobalWidgetsLocalizations.delegate, GlobalCupertinoLocalizations.delegate, ], - supportedLocales: S.delegate.supportedLocales, + supportedLocales: [ + Locale.fromSubtags(languageCode: 'ru'), + ], ), ), ), diff --git a/pubspec.yaml b/pubspec.yaml index 341123b1..9a4a500b 100644 --- a/pubspec.yaml +++ b/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