From 7a846ed218f4edd4346a4eb2172c9f0588766d4a Mon Sep 17 00:00:00 2001 From: mso7nueh Date: Fri, 26 Dec 2025 13:35:08 +0300 Subject: [PATCH 1/3] chore: add blank lines for improved readability in multiple files --- .../oauth_verification_confirm_response.dart | 3 + .../models/oauth_verification_request.dart | 3 + .../oauth_verification_request_response.dart | 3 + .../pages/forget_password_screen.dart | 290 +++---- .../barcode/services/barcode_service.dart | 3 + .../pages/registration_screen.dart | 766 +++++++++--------- .../cubit/registration_cubit.dart | 504 ++++++------ .../cubit/registration_state.dart | 65 +- .../widgets/second_step_content.dart | 3 +- ..._successful_registration_bottom_sheet.dart | 217 ++--- test/core/constants/app_constants_test.dart | 3 + test/core/cubit/state_utils_test.dart | 3 + .../core/data/selection_popup_model_test.dart | 3 + test/core/enums/prefs_keys_test.dart | 3 + .../internet_connection_state_test.dart | 3 + test/core/prefs_keys_test.dart | 3 + .../routing/main_navigation_routes_test.dart | 3 + test/core/routing/other_routes_test.dart | 3 + test/core/routing/route_constants_test.dart | 3 + test/core/routing/settings_routes_test.dart | 3 + test/core/views/cubit/views_state_test.dart | 3 + test/core/widgets/add_item_button_test.dart | 3 + .../widgets/animated_option_button_test.dart | 3 + test/core/widgets/base_button_test.dart | 3 + .../widgets/bottom_sheet_with_blur_test.dart | 3 + test/core/widgets/cpfc_container_test.dart | 3 + test/core/widgets/custom_search_bar_test.dart | 3 + test/core/widgets/item_widget_test.dart | 3 + test/core/widgets/loader_test.dart | 3 + .../widgets/main_background_bouble_test.dart | 3 + .../widgets/marketing_bottom_sheet_test.dart | 3 + test/core/widgets/option_widget_test.dart | 3 + .../widgets/error_widget_test.dart | 3 + .../chosen_diet_widget_content_test.dart | 3 + .../widgets/comment_container_test.dart | 3 + .../finish_day_button_widget_test.dart | 3 + .../widgets/meal_summary_widget_test.dart | 3 + .../widgets/product_loader_widget_test.dart | 3 + .../widgets/info_bouble_test.dart | 3 + .../widgets/choice_chip_widget_test.dart | 3 + .../widgets/filter_badge_test.dart | 3 + .../widgets/status_widget_test.dart | 3 + .../glossary_repository_test.dart | 3 + .../widgets/notification_widget_test.dart | 3 + .../service/notifications_service_test.dart | 3 + 45 files changed, 1041 insertions(+), 921 deletions(-) diff --git a/lib/features/auth/models/oauth_verification_confirm_response.dart b/lib/features/auth/models/oauth_verification_confirm_response.dart index 5f86f75e..85badaed 100644 --- a/lib/features/auth/models/oauth_verification_confirm_response.dart +++ b/lib/features/auth/models/oauth_verification_confirm_response.dart @@ -31,3 +31,6 @@ abstract class OAuthVerificationConfirmResponse with _$OAuthVerificationConfirmR + + + diff --git a/lib/features/auth/models/oauth_verification_request.dart b/lib/features/auth/models/oauth_verification_request.dart index 8aa40c1c..50970e6a 100644 --- a/lib/features/auth/models/oauth_verification_request.dart +++ b/lib/features/auth/models/oauth_verification_request.dart @@ -25,3 +25,6 @@ abstract class OAuthVerificationRequest with _$OAuthVerificationRequest { + + + diff --git a/lib/features/auth/models/oauth_verification_request_response.dart b/lib/features/auth/models/oauth_verification_request_response.dart index 062c14da..3525051e 100644 --- a/lib/features/auth/models/oauth_verification_request_response.dart +++ b/lib/features/auth/models/oauth_verification_request_response.dart @@ -25,3 +25,6 @@ abstract class OAuthVerificationRequestResponse with _$OAuthVerificationRequestR + + + diff --git a/lib/features/auth/presentation/pages/forget_password_screen.dart b/lib/features/auth/presentation/pages/forget_password_screen.dart index d842fcfc..911805c8 100644 --- a/lib/features/auth/presentation/pages/forget_password_screen.dart +++ b/lib/features/auth/presentation/pages/forget_password_screen.dart @@ -1,166 +1,124 @@ -import 'package:eatvio_app/core/locator_service.dart'; -import 'package:eatvio_app/core/theme/theme_helper.dart'; -import 'package:eatvio_app/core/utils/toasts.dart'; -import 'package:eatvio_app/core/widgets/custom_text_form_field.dart'; -import 'package:eatvio_app/features/auth/presentation/bloc/reset_password_cubit/reset_password_cubit.dart'; -import 'package:eatvio_app/features/auth/presentation/bloc/reset_password_cubit/reset_password_state.dart'; -import 'package:flutter/material.dart'; -import 'package:flutter_bloc/flutter_bloc.dart'; -import 'package:go_router/go_router.dart'; - -class ForgetPasswordScreen extends StatefulWidget { - final bool isAuth; - const ForgetPasswordScreen({super.key, required this.isAuth}); - - @override - State createState() => _ForgetPasswordScreenState(); -} - -class _ForgetPasswordScreenState extends State { - final emailController = TextEditingController(); - final codeController = TextEditingController(); - final passwordController = TextEditingController(); - final passwordConfirmController = TextEditingController(); - - @override - Widget build(BuildContext context) { - return BlocProvider( - create: (BuildContext context) => sl(), - child: BlocConsumer( - builder: (BuildContext context, ResetPasswordState state) { - return Scaffold( - appBar: AppBar( - title: widget.isAuth ? const Text('Смена пароля') : const Text('Восстановление пароля'), - centerTitle: false, - ), - body: state is ResetPasswordStateLoadingResetPassword || state is ResetPasswordStateLoadingSendEmailCode - ? const Center( - child: CircularProgressIndicator(), - ) - : Padding( - padding: const EdgeInsets.all(16), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - if (state is ResetPasswordStateCodeSendError || state is ResetPasswordStateEmpty) ...[ - Text( - '${widget.isAuth ? '' : 'Забыли пароль? '}Укажите свой email,\nи мы пришлём код для сброса.', - style: const TextStyle( - fontSize: 16, - fontWeight: FontWeight.w500, - ), - ), - const SizedBox(height: 24), - const Text( - 'E-mail', - style: TextStyle( - fontSize: 14, - fontWeight: FontWeight.w500, - ), - ), - const SizedBox(height: 4), - CustomTextFormField( - controller: emailController, - hintText: 'Введите e-mail', - ), - ], - if (state is ResetPasswordStateCodeSendSuccess || state is ResetPasswordStateResetPasswordError) ...[ - const Text( - 'Код', - style: TextStyle( - fontSize: 14, - fontWeight: FontWeight.w500, - ), - ), - const SizedBox(height: 4), - CustomTextFormField( - controller: codeController, - hintText: 'Введите код', - ), - const SizedBox(height: 12), - const Text( - 'Пароль', - style: TextStyle( - fontSize: 14, - fontWeight: FontWeight.w500, - ), - ), - const SizedBox(height: 4), - CustomTextFormField( - controller: passwordController, - hintText: 'Введите новый пароль', - ), - const SizedBox(height: 12), - const Text( - 'Подтвердите пароль', - style: TextStyle( - fontSize: 14, - fontWeight: FontWeight.w500, - ), - ), - const SizedBox(height: 4), - CustomTextFormField( - controller: passwordConfirmController, - hintText: 'Введите новый пароль', - ), - ], - const Spacer(), - InkWell( - onTap: () { - if (state is ResetPasswordStateCodeSendSuccess || state is ResetPasswordStateResetPasswordError) { - context.read().tryResetPassword( - emailController.text, - codeController.text, - passwordController.text, - passwordConfirmController.text, - ); - } else { - context.read().trySendEmailCode(emailController.text); - } - }, - borderRadius: BorderRadius.circular(20), - child: Ink( - width: MediaQuery.of(context).size.width - 32, - padding: const EdgeInsets.all(16), - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(20), - color: appTheme.blueA400, - ), - child: Center( - child: Text( - state is ResetPasswordStateCodeSendSuccess || state is ResetPasswordStateResetPasswordError - ? 'Сменить пароль' - : 'Сбросить пароль', - style: const TextStyle( - fontSize: 18, - fontWeight: FontWeight.w600, - color: Colors.white, - ), - ), - ), - ), - ), - ], - ), - ), - ); - }, - listener: (BuildContext context, ResetPasswordState state) { - if (state is ResetPasswordStateCodeSendSuccess) { - showSuccessToast('Код на почту успешно отправлен'); - } - if (state is ResetPasswordStateCodeSendError) { - showErrorToast('Ошибка отправки кода. Проверьте правильность ввёдной почты'); - } - if (state is ResetPasswordStateResetPasswordError) { - showErrorToast('Ошибка смены пароля. Проверьте правильность ввёднного кода и совпадение пароля'); - } - if (state is ResetPasswordStateResetPassowrdSuccess) { - GoRouter.of(context).pop(); - showSuccessToast('Пароль успешно сменён. ${widget.isAuth ? '' : 'Войдите в аккаунт'}'); - } - }, - ), - ); - } -} +import 'package:eatvio_app/core/locator_service.dart'; +import 'package:eatvio_app/core/theme/theme_helper.dart'; +import 'package:eatvio_app/core/utils/toasts.dart'; +import 'package:eatvio_app/core/widgets/custom_text_form_field.dart'; +import 'package:eatvio_app/features/auth/presentation/bloc/reset_password_cubit/reset_password_cubit.dart'; +import 'package:eatvio_app/features/auth/presentation/bloc/reset_password_cubit/reset_password_state.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:go_router/go_router.dart'; + +class ForgetPasswordScreen extends StatefulWidget { + final bool isAuth; + const ForgetPasswordScreen({super.key, required this.isAuth}); + + @override + State createState() => _ForgetPasswordScreenState(); +} + +class _ForgetPasswordScreenState extends State { + final emailController = TextEditingController(); + final codeController = TextEditingController(); + final passwordController = TextEditingController(); + final passwordConfirmController = TextEditingController(); + + @override + Widget build(BuildContext context) { + return BlocProvider( + create: (BuildContext context) => sl(), + child: BlocConsumer( + builder: (BuildContext context, ResetPasswordState state) { + return Scaffold( + appBar: AppBar(title: widget.isAuth ? const Text('Смена пароля') : const Text('Восстановление пароля'), centerTitle: false), + body: state is ResetPasswordStateLoadingResetPassword || state is ResetPasswordStateLoadingSendEmailCode + ? const Center(child: CircularProgressIndicator()) + : Padding( + padding: const EdgeInsets.all(16), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + if (state is ResetPasswordStateCodeSendError || state is ResetPasswordStateEmpty) ...[ + Text( + '${widget.isAuth ? '' : 'Забыли пароль? '}Укажите свой email,\nи мы пришлём код для сброса.', + style: const TextStyle(fontSize: 16, fontWeight: FontWeight.w500), + ), + const SizedBox(height: 24), + const Text('E-mail', style: TextStyle(fontSize: 14, fontWeight: FontWeight.w500)), + const SizedBox(height: 4), + CustomTextFormField(controller: emailController, hintText: 'Введите e-mail'), + ], + if (state is ResetPasswordStateCodeSendSuccess || state is ResetPasswordStateResetPasswordError) ...[ + const Text('Код', style: TextStyle(fontSize: 14, fontWeight: FontWeight.w500)), + const SizedBox(height: 4), + CustomTextFormField(controller: codeController, hintText: 'Введите код'), + const SizedBox(height: 12), + const Text('Пароль', style: TextStyle(fontSize: 14, fontWeight: FontWeight.w500)), + const SizedBox(height: 4), + CustomTextFormField( + controller: passwordController, + hintText: 'Введите новый пароль', + inputFormatters: [FilteringTextInputFormatter.deny(RegExp(r'\s'))], + ), + const SizedBox(height: 12), + const Text('Подтвердите пароль', style: TextStyle(fontSize: 14, fontWeight: FontWeight.w500)), + const SizedBox(height: 4), + CustomTextFormField( + controller: passwordConfirmController, + hintText: 'Введите новый пароль', + inputFormatters: [FilteringTextInputFormatter.deny(RegExp(r'\s'))], + ), + ], + const Spacer(), + InkWell( + onTap: () { + if (state is ResetPasswordStateCodeSendSuccess || state is ResetPasswordStateResetPasswordError) { + context.read().tryResetPassword( + emailController.text, + codeController.text, + passwordController.text, + passwordConfirmController.text, + ); + } else { + context.read().trySendEmailCode(emailController.text); + } + }, + borderRadius: BorderRadius.circular(20), + child: Ink( + width: MediaQuery.of(context).size.width - 32, + padding: const EdgeInsets.all(16), + decoration: BoxDecoration(borderRadius: BorderRadius.circular(20), color: appTheme.blueA400), + child: Center( + child: Text( + state is ResetPasswordStateCodeSendSuccess || state is ResetPasswordStateResetPasswordError + ? 'Сменить пароль' + : 'Сбросить пароль', + style: const TextStyle(fontSize: 18, fontWeight: FontWeight.w600, color: Colors.white), + ), + ), + ), + ), + ], + ), + ), + ); + }, + listener: (BuildContext context, ResetPasswordState state) { + if (state is ResetPasswordStateCodeSendSuccess) { + showSuccessToast('Код на почту успешно отправлен'); + } + if (state is ResetPasswordStateCodeSendError) { + showErrorToast('Ошибка отправки кода. Проверьте правильность ввёдной почты'); + } + if (state is ResetPasswordStateResetPasswordError) { + showErrorToast('Ошибка смены пароля. Проверьте правильность ввёднного кода и совпадение пароля'); + } + if (state is ResetPasswordStateResetPassowrdSuccess) { + GoRouter.of(context).pop(); + showSuccessToast('Пароль успешно сменён. ${widget.isAuth ? '' : 'Войдите в аккаунт'}'); + } + }, + ), + ); + } +} diff --git a/lib/features/barcode/services/barcode_service.dart b/lib/features/barcode/services/barcode_service.dart index 4bfc2aa3..e64e2575 100644 --- a/lib/features/barcode/services/barcode_service.dart +++ b/lib/features/barcode/services/barcode_service.dart @@ -19,3 +19,6 @@ abstract class BarcodeService { + + + diff --git a/lib/features/registration/presentation/pages/registration_screen.dart b/lib/features/registration/presentation/pages/registration_screen.dart index b00df3ea..759df201 100644 --- a/lib/features/registration/presentation/pages/registration_screen.dart +++ b/lib/features/registration/presentation/pages/registration_screen.dart @@ -1,378 +1,388 @@ -import 'package:eatvio_app/core/utils/image_constant.dart'; -import 'package:eatvio_app/core/widgets/loader.dart'; -import 'package:eatvio_app/features/registration/presentation/bloc/registration_cubit/registration_cubit.dart'; -import 'package:eatvio_app/features/registration/presentation/bloc/registration_cubit/registration_state.dart'; -import 'package:eatvio_app/features/registration/presentation/pages/additional_information_screen.dart'; -import 'package:eatvio_app/features/registration/presentation/pages/allergies_screen.dart'; -import 'package:eatvio_app/features/registration/presentation/pages/blood_pressure_screen.dart'; -import 'package:eatvio_app/features/registration/presentation/pages/body_circumferences_screen.dart'; -import 'package:eatvio_app/features/registration/presentation/pages/city_of_living_screen.dart'; -import 'package:eatvio_app/features/registration/presentation/pages/credentials_screen.dart'; -import 'package:eatvio_app/features/registration/presentation/pages/date_of_birth_screen.dart'; -import 'package:eatvio_app/features/registration/presentation/pages/favorite_foods_screen.dart'; -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/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/features/registration/services/registration_analytics_service.dart'; -import 'package:eatvio_app/generated/l10n.dart'; -import 'package:eatvio_app/core/locator_service.dart'; -import 'package:eatvio_app/core/theme/theme_helper.dart'; -import 'package:flutter/material.dart'; -import 'package:flutter_bloc/flutter_bloc.dart'; -import 'package:flutter_svg/flutter_svg.dart'; -import 'package:go_router/go_router.dart'; -import 'package:validators/validators.dart'; - -class RegistrationScreen extends StatefulWidget { - const RegistrationScreen({super.key}); - - @override - State createState() => _RegistrationScreenState(); -} - -class _RegistrationScreenState extends State { - double progressValue = 1 / 15; - int step = 1; - final pageController = PageController(); - final RegistrationCubit registrationCubit = sl(); - - @override - void initState() { - super.initState(); - RegistrationAnalyticsService.reportRegistrationStep(step); - } - - @override - Widget build(BuildContext context) { - return BlocProvider( - create: (BuildContext context) => registrationCubit, - child: BlocListener( - listener: (context, state) { - if (state.error != null && state.error!.isNotEmpty) { - showDialog( - context: context, - builder: (context) => AlertDialog( - title: const Text('Ошибка при регистрации'), - content: Text(state.error!), - actions: [TextButton(onPressed: () => Navigator.pop(context), child: const Text('OK'))], - ), - ); - } - }, - child: BlocBuilder( - builder: (BuildContext context, RegistrationState state) { - return Scaffold( - floatingActionButtonLocation: FloatingActionButtonLocation.centerFloat, - floatingActionButton: Row( - children: [ - Container( - height: 48, - width: MediaQuery.of(context).size.width * 0.2, - padding: const EdgeInsets.only(left: 16, right: 4), - child: FloatingActionButton.extended( - heroTag: 'backButtonRegistration', - highlightElevation: 0, - focusElevation: 0, - elevation: 0, - shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(20)), - backgroundColor: const Color(0xFFEAF2FD), - onPressed: () async { - setState(() { - pageController.previousPage(duration: const Duration(milliseconds: 350), curve: Curves.easeInOut); - }); - }, - label: Icon(Icons.arrow_back_ios_new_rounded, color: appTheme.blueA400), - ), - ), - Container( - height: 48, - width: MediaQuery.of(context).size.width * 0.8, - padding: const EdgeInsets.only(left: 4, right: 16), - child: FloatingActionButton.extended( - heroTag: 'nextButtonRegistration', - highlightElevation: 0, - elevation: 0, - focusElevation: 0, - shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(20)), - backgroundColor: appTheme.blueA400, - onPressed: () async { - if (await canGoNext(state, context)) { - setState(() { - pageController.nextPage(duration: const Duration(milliseconds: 350), curve: Curves.easeInOut); - }); - } - }, - label: Text('Продолжить', style: theme.textTheme.titleSmall!.copyWith(color: Colors.white)), - ), - ), - ], - ), - appBar: AppBar( - automaticallyImplyLeading: false, - leading: Builder( - builder: (context) => IconButton(icon: const Icon(Icons.close_rounded), onPressed: () => GoRouter.of(context).pop()), - ), - centerTitle: true, - title: SvgPicture.asset(ImageConstant.imgLogoEatvio, height: 48, fit: BoxFit.contain), - actions: [Padding(padding: const EdgeInsets.only(right: 16), child: Text('шаг $step из 15'))], - bottom: PreferredSize( - preferredSize: Size(MediaQuery.of(context).size.width, 32), - child: Padding( - padding: const EdgeInsets.all(16), - child: TweenAnimationBuilder( - duration: const Duration(milliseconds: 350), - curve: Curves.easeInOut, - tween: Tween(begin: 1 / 15, end: progressValue), - builder: (context, value, _) => LinearProgressIndicator( - value: value, - borderRadius: BorderRadius.circular(16), - minHeight: 8, - backgroundColor: Colors.grey.shade100, - valueColor: AlwaysStoppedAnimation(appTheme.blueA400), - ), - ), - ), - ), - ), - body: PageView( - physics: const NeverScrollableScrollPhysics(), - controller: pageController, - onPageChanged: (index) { - setState(() { - step = index + 1; - progressValue = step / 15; - }); - // Report analytics for step completion - RegistrationAnalyticsService.reportRegistrationStep(step); - }, - children: [ - // LanguageSelectScreen(sheet: state.sheet), - PhoneScreen(sheet: state.sheet), - CredentialsScreen(sheet: state.sheet), - NameScreen(sheet: state.sheet), - GenderScreen(sheet: state.sheet), - DateOfBirthScreen(sheet: state.sheet), - CityOfLivingScreen(sheet: state.sheet), - BodyCircumferencesScreen(sheet: state.sheet), - WeightScreen(sheet: state.sheet), - HeightScreen(sheet: state.sheet), - BloodPressureScreen(sheet: state.sheet), - AllergiesScreen(sheet: state.sheet), - MedicineIntakeScreen(sheet: state.sheet), - FavoriteSportsScreen(sheet: state.sheet), - FavoriteFoodsScreen(sheet: state.sheet), - AdditionalInformationScreen(sheet: state.sheet), - ], - ), - ); - }, - ), - ), - ); - } - - Future canGoNext(RegistrationState state, BuildContext context) async { - switch (step) { - case 1: - if (state.sheet.phone.length != 18) { - showDialog( - context: context, - builder: (context) => AlertDialog( - title: const Text('Ошибка'), - content: const Text('Введите корректный номер телефона'), - actions: [TextButton(onPressed: () => Navigator.pop(context), child: const Text('OK'))], - ), - ); - return false; - } else { - return true; - } - case 2: - List errors = validateCredentialsFields(state.sheet.email, state.sheet.password, state.sheet.passwordConfirmation, context); - if (errors.isEmpty) { - return true; - } else { - String errorMessage = ''; - - for (var error in errors) { - errorMessage += '$error\n\n'; - } - - showDialog( - context: context, - builder: (context) => AlertDialog( - title: const Text('Ошибка'), - content: Text(errorMessage), - actions: [TextButton(onPressed: () => Navigator.pop(context), child: const Text('OK'))], - ), - ); - return false; - } - case 3: - List errors = validateNameFields(state.sheet.firstName.toString(), state.sheet.lastName.toString(), context); - if (errors.isEmpty) { - return true; - } else { - String errorMessage = ''; - - for (var error in errors) { - errorMessage += '$error\n\n'; - } - - showDialog( - context: context, - builder: (context) => AlertDialog( - title: const Text('Ошибка'), - content: Text(errorMessage), - actions: [TextButton(onPressed: () => Navigator.pop(context), child: const Text('OK'))], - ), - ); - return false; - } - case 4: - if (state.sheet.gender.isNotEmpty) { - return true; - } else { - showDialog( - context: context, - builder: (context) => AlertDialog( - title: const Text('Ошибка'), - content: const Text('Выберете пол'), - actions: [TextButton(onPressed: () => Navigator.pop(context), child: const Text('OK'))], - ), - ); - return false; - } - case 6: - if (state.sheet.location.isNotEmpty) { - return true; - } else { - showDialog( - context: context, - builder: (context) => AlertDialog( - title: const Text('Ошибка'), - content: const Text('Заполните все поля'), - actions: [TextButton(onPressed: () => Navigator.pop(context), child: const Text('OK'))], - ), - ); - return false; - } - case 7: - List errors = validateCircumferencesFields( - state.sheet.chestCircumference.toString(), - state.sheet.waistCircumference.toString(), - state.sheet.hipCircumference.toString(), - context, - ); - if (errors.isEmpty) { - return true; - } else { - String errorMessage = ''; - - for (var error in errors) { - errorMessage += '$error\n\n'; - } - - showDialog( - context: context, - builder: (context) => AlertDialog( - title: const Text('Ошибка'), - content: Text(errorMessage), - actions: [TextButton(onPressed: () => Navigator.pop(context), child: const Text('OK'))], - ), - ); - return false; - } - case 15: - try { - showDialog( - barrierDismissible: false, - useSafeArea: false, - context: context, - builder: (context) => const PopScope(canPop: false, child: Loader()), - ); - await registrationCubit.tryRegistration(); - if (context.mounted) { - Navigator.pop(context); - } - } catch (e) { - if (context.mounted) { - Navigator.pop(context); - showDialog( - context: context, - builder: (context) => const AlertDialog(title: Text('Ошибка при попытке регистрации')), - ); - } - } - return false; - default: - return true; - } - } - - List validateCircumferencesFields(String chestCircumference, String waistCircumference, String hipCircumference, BuildContext context) { - final s = S.of(context); - - List errors = []; - - if (chestCircumference.isEmpty | waistCircumference.isEmpty | hipCircumference.isEmpty) { - errors.add(s.circumference_empty); - } - - if (![chestCircumference, waistCircumference, hipCircumference].every((circumference) => int.tryParse(circumference) != null)) { - errors.add(s.circumference_is_not_a_number); - } - - return errors; - } - - List validateCredentialsFields(String email, String password, String passwordConfirmation, BuildContext context) { - final s = S.of(context); - - List errors = []; - - if (!isEmail(email)) { - errors.add(s.email_invalid); - } - - if (email.isEmpty) { - errors.add(s.email_empty); - } - - if (password.length < 6) { - errors.add(s.password_short); - } - - if (password != passwordConfirmation) { - errors.add(s.password_confirmation_mismatch); - } - - return errors; - } - - List validateNameFields(String firstName, String lastName, BuildContext context) { - List errors = []; - - if (firstName.isEmpty) { - errors.add('First name cannot be empty'); - } - - if (firstName.isEmpty) { - errors.add('First name is too short'); - } - - if (lastName.isEmpty) { - errors.add('Last name cannot be empty'); - } - - if (lastName.isEmpty) { - errors.add('Last name is too short'); - } - - return errors; - } -} +import 'package:eatvio_app/core/utils/image_constant.dart'; +import 'package:eatvio_app/core/widgets/loader.dart'; +import 'package:eatvio_app/features/registration/presentation/bloc/registration_cubit/registration_cubit.dart'; +import 'package:eatvio_app/features/registration/presentation/bloc/registration_cubit/registration_state.dart'; +import 'package:eatvio_app/features/registration/presentation/pages/additional_information_screen.dart'; +import 'package:eatvio_app/features/registration/presentation/pages/allergies_screen.dart'; +import 'package:eatvio_app/features/registration/presentation/pages/blood_pressure_screen.dart'; +import 'package:eatvio_app/features/registration/presentation/pages/body_circumferences_screen.dart'; +import 'package:eatvio_app/features/registration/presentation/pages/city_of_living_screen.dart'; +import 'package:eatvio_app/features/registration/presentation/pages/credentials_screen.dart'; +import 'package:eatvio_app/features/registration/presentation/pages/date_of_birth_screen.dart'; +import 'package:eatvio_app/features/registration/presentation/pages/favorite_foods_screen.dart'; +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/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/features/registration/services/registration_analytics_service.dart'; +import 'package:eatvio_app/generated/l10n.dart'; +import 'package:eatvio_app/core/locator_service.dart'; +import 'package:eatvio_app/core/theme/theme_helper.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:flutter_svg/flutter_svg.dart'; +import 'package:go_router/go_router.dart'; +import 'package:validators/validators.dart'; + +class RegistrationScreen extends StatefulWidget { + const RegistrationScreen({super.key}); + + @override + State createState() => _RegistrationScreenState(); +} + +class _RegistrationScreenState extends State { + double progressValue = 1 / 15; + int step = 1; + final pageController = PageController(); + final RegistrationCubit registrationCubit = sl(); + + @override + void initState() { + super.initState(); + RegistrationAnalyticsService.reportRegistrationStep(step); + } + + @override + Widget build(BuildContext context) { + return BlocProvider( + create: (BuildContext context) => registrationCubit, + child: BlocListener( + listener: (context, state) { + if (state.error != null && state.error!.isNotEmpty) { + showDialog( + context: context, + builder: (context) => AlertDialog( + title: const Text('Ошибка при регистрации'), + content: Text(state.error!), + actions: [TextButton(onPressed: () => Navigator.pop(context), child: const Text('OK'))], + ), + ); + } + }, + child: BlocBuilder( + builder: (BuildContext context, RegistrationState state) { + return Scaffold( + floatingActionButtonLocation: FloatingActionButtonLocation.centerFloat, + floatingActionButton: Row( + children: [ + Container( + height: 48, + width: MediaQuery.of(context).size.width * 0.2, + padding: const EdgeInsets.only(left: 16, right: 4), + child: FloatingActionButton.extended( + heroTag: 'backButtonRegistration', + highlightElevation: 0, + focusElevation: 0, + elevation: 0, + shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(20)), + backgroundColor: const Color(0xFFEAF2FD), + onPressed: () async { + setState(() { + pageController.previousPage(duration: const Duration(milliseconds: 350), curve: Curves.easeInOut); + }); + }, + label: Icon(Icons.arrow_back_ios_new_rounded, color: appTheme.blueA400), + ), + ), + Container( + height: 48, + width: MediaQuery.of(context).size.width * 0.8, + padding: const EdgeInsets.only(left: 4, right: 16), + child: FloatingActionButton.extended( + heroTag: 'nextButtonRegistration', + highlightElevation: 0, + elevation: 0, + focusElevation: 0, + shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(20)), + backgroundColor: appTheme.blueA400, + onPressed: () async { + if (await canGoNext(state, context)) { + setState(() { + pageController.nextPage(duration: const Duration(milliseconds: 350), curve: Curves.easeInOut); + }); + } + }, + label: Text('Продолжить', style: theme.textTheme.titleSmall!.copyWith(color: Colors.white)), + ), + ), + ], + ), + appBar: AppBar( + automaticallyImplyLeading: false, + leading: Builder( + builder: (context) => IconButton(icon: const Icon(Icons.close_rounded), onPressed: () => GoRouter.of(context).pop()), + ), + centerTitle: true, + title: SvgPicture.asset(ImageConstant.imgLogoEatvio, height: 48, fit: BoxFit.contain), + actions: [Padding(padding: const EdgeInsets.only(right: 16), child: Text('шаг $step из 15'))], + bottom: PreferredSize( + preferredSize: Size(MediaQuery.of(context).size.width, 32), + child: Padding( + padding: const EdgeInsets.all(16), + child: TweenAnimationBuilder( + duration: const Duration(milliseconds: 350), + curve: Curves.easeInOut, + tween: Tween(begin: 1 / 15, end: progressValue), + builder: (context, value, _) => LinearProgressIndicator( + value: value, + borderRadius: BorderRadius.circular(16), + minHeight: 8, + backgroundColor: Colors.grey.shade100, + valueColor: AlwaysStoppedAnimation(appTheme.blueA400), + ), + ), + ), + ), + ), + body: PageView( + physics: const NeverScrollableScrollPhysics(), + controller: pageController, + onPageChanged: (index) { + setState(() { + step = index + 1; + progressValue = step / 15; + }); + // Report analytics for step completion + RegistrationAnalyticsService.reportRegistrationStep(step); + }, + children: [ + // LanguageSelectScreen(sheet: state.sheet), + PhoneScreen(sheet: state.sheet), + CredentialsScreen(sheet: state.sheet), + NameScreen(sheet: state.sheet), + GenderScreen(sheet: state.sheet), + DateOfBirthScreen(sheet: state.sheet), + CityOfLivingScreen(sheet: state.sheet), + BodyCircumferencesScreen(sheet: state.sheet), + WeightScreen(sheet: state.sheet), + HeightScreen(sheet: state.sheet), + BloodPressureScreen(sheet: state.sheet), + AllergiesScreen(sheet: state.sheet), + MedicineIntakeScreen(sheet: state.sheet), + FavoriteSportsScreen(sheet: state.sheet), + FavoriteFoodsScreen(sheet: state.sheet), + AdditionalInformationScreen(sheet: state.sheet), + ], + ), + ); + }, + ), + ), + ); + } + + Future canGoNext(RegistrationState state, BuildContext context) async { + switch (step) { + case 1: + if (state.sheet.phone.length != 18) { + showDialog( + context: context, + builder: (context) => AlertDialog( + title: const Text('Ошибка'), + content: const Text('Введите корректный номер телефона'), + actions: [TextButton(onPressed: () => Navigator.pop(context), child: const Text('OK'))], + ), + ); + return false; + } else { + return true; + } + case 2: + List errors = validateCredentialsFields(state.sheet.email, state.sheet.password, state.sheet.passwordConfirmation, context); + if (errors.isEmpty) { + return true; + } else { + String errorMessage = ''; + + for (var error in errors) { + errorMessage += '$error\n\n'; + } + + showDialog( + context: context, + builder: (context) => AlertDialog( + title: const Text('Ошибка'), + content: Text(errorMessage), + actions: [TextButton(onPressed: () => Navigator.pop(context), child: const Text('OK'))], + ), + ); + return false; + } + case 3: + List errors = validateNameFields(state.sheet.firstName.toString(), state.sheet.lastName.toString(), context); + if (errors.isEmpty) { + return true; + } else { + String errorMessage = ''; + + for (var error in errors) { + errorMessage += '$error\n\n'; + } + + showDialog( + context: context, + builder: (context) => AlertDialog( + title: const Text('Ошибка'), + content: Text(errorMessage), + actions: [TextButton(onPressed: () => Navigator.pop(context), child: const Text('OK'))], + ), + ); + return false; + } + case 4: + if (state.sheet.gender.isNotEmpty) { + return true; + } else { + showDialog( + context: context, + builder: (context) => AlertDialog( + title: const Text('Ошибка'), + content: const Text('Выберете пол'), + actions: [TextButton(onPressed: () => Navigator.pop(context), child: const Text('OK'))], + ), + ); + return false; + } + case 6: + if (state.sheet.location.isNotEmpty) { + return true; + } else { + showDialog( + context: context, + builder: (context) => AlertDialog( + title: const Text('Ошибка'), + content: const Text('Заполните все поля'), + actions: [TextButton(onPressed: () => Navigator.pop(context), child: const Text('OK'))], + ), + ); + return false; + } + case 7: + List errors = validateCircumferencesFields( + state.sheet.chestCircumference.toString(), + state.sheet.waistCircumference.toString(), + state.sheet.hipCircumference.toString(), + context, + ); + if (errors.isEmpty) { + return true; + } else { + String errorMessage = ''; + + for (var error in errors) { + errorMessage += '$error\n\n'; + } + + showDialog( + context: context, + builder: (context) => AlertDialog( + title: const Text('Ошибка'), + content: Text(errorMessage), + actions: [TextButton(onPressed: () => Navigator.pop(context), child: const Text('OK'))], + ), + ); + return false; + } + case 15: + try { + showDialog( + barrierDismissible: false, + useSafeArea: false, + context: context, + builder: (context) => const PopScope(canPop: false, child: Loader()), + ); + await registrationCubit.tryRegistration(); + if (context.mounted) { + Navigator.pop(context); + } + } catch (e) { + if (context.mounted) { + Navigator.pop(context); + String errorMessage = ''; + if (e is String) { + errorMessage = e; + } else { + errorMessage = e.toString().replaceFirst('Exception: ', '').replaceFirst('Error: ', ''); + } + showDialog( + context: context, + builder: (context) => AlertDialog( + title: const Text('Ошибка при регистрации'), + content: Text(errorMessage.isNotEmpty ? errorMessage : 'Регистрация не удалась'), + actions: [TextButton(onPressed: () => Navigator.pop(context), child: const Text('OK'))], + ), + ); + } + } + return false; + default: + return true; + } + } + + List validateCircumferencesFields(String chestCircumference, String waistCircumference, String hipCircumference, BuildContext context) { + final s = S.of(context); + + List errors = []; + + if (chestCircumference.isEmpty | waistCircumference.isEmpty | hipCircumference.isEmpty) { + errors.add(s.circumference_empty); + } + + if (![chestCircumference, waistCircumference, hipCircumference].every((circumference) => int.tryParse(circumference) != null)) { + errors.add(s.circumference_is_not_a_number); + } + + return errors; + } + + List validateCredentialsFields(String email, String password, String passwordConfirmation, BuildContext context) { + final s = S.of(context); + + List errors = []; + + if (!isEmail(email)) { + errors.add(s.email_invalid); + } + + if (email.isEmpty) { + errors.add(s.email_empty); + } + + if (password.length < 6) { + errors.add(s.password_short); + } + + if (password != passwordConfirmation) { + errors.add(s.password_confirmation_mismatch); + } + + return errors; + } + + List validateNameFields(String firstName, String lastName, BuildContext context) { + List errors = []; + + if (firstName.isEmpty) { + errors.add('First name cannot be empty'); + } + + if (firstName.isEmpty) { + errors.add('First name is too short'); + } + + if (lastName.isEmpty) { + errors.add('Last name cannot be empty'); + } + + if (lastName.isEmpty) { + errors.add('Last name is too short'); + } + + return errors; + } +} diff --git a/lib/features/registration_v2/cubit/registration_cubit.dart b/lib/features/registration_v2/cubit/registration_cubit.dart index 67cd5a0d..49d4eabc 100644 --- a/lib/features/registration_v2/cubit/registration_cubit.dart +++ b/lib/features/registration_v2/cubit/registration_cubit.dart @@ -1,238 +1,266 @@ -import 'dart:developer'; - -import 'package:dio/dio.dart'; -import 'package:eatvio_app/core/token_storage.dart'; -import 'package:eatvio_app/core/utils/date_time_utils.dart'; -import 'package:eatvio_app/features/registration_v2/cubit/registration_state.dart'; -import 'package:eatvio_app/features/registration_v2/models/registration_model.dart'; -import 'package:eatvio_app/features/registration_v2/repositories/registration_repository_v2.dart'; -import 'package:flutter_bloc/flutter_bloc.dart'; -import 'package:gpassword/gpassword.dart'; - -class RegistrationCubitV2 extends Cubit { - final RegistrationRepositoryV2 registrationRepository; - RegistrationCubitV2({required this.registrationRepository}) : super(RegistrationStateV2()); - - void setFirstName(String firstName) { - emit(state.copyWith(firstName: firstName)); - validateFirstName(); - } - - void setLastName(String lastName) { - emit(state.copyWith(lastName: lastName)); - validateLastName(); - } - - void setEmail(String email) { - emit(state.copyWith(email: email)); - validateEmail(); - } - - void setPhone(String phone) { - emit(state.copyWith(phone: phone)); - validatePhone(); - } - - void setBirthday(DateTime birthday) { - emit(state.copyWith(birthday: birthday)); - validateBirthday(); - } - - void setGender(String gender) { - emit(state.copyWith(gender: gender)); - validateGender(); - } - - void setWeight(int weight) { - emit(state.copyWith(weight: weight)); - validateWeight(); - } - - void setHeight(int height) { - emit(state.copyWith(height: height)); - validateHeight(); - } - - void setActivityCoefficient(double activityCoefficient) { - emit(state.copyWith(activityCoefficient: activityCoefficient)); - validateActivityCoefficient(); - } - - void setPassword(String password) { - emit(state.copyWith(password: password)); - } - - bool validateFirstName() { - if (state.firstName.isEmpty) { - emit(state.copyWith(isFirstNameValid: false)); - return false; - } - emit(state.copyWith(isFirstNameValid: true)); - return true; - } - - bool validateLastName() { - if (state.lastName.isEmpty) { - emit(state.copyWith(isLastNameValid: false)); - return false; - } - emit(state.copyWith(isLastNameValid: true)); - return true; - } - - bool validateEmail() { - if (state.email.isEmpty) { - emit(state.copyWith(isEmailValid: false)); - return false; - } - emit(state.copyWith(isEmailValid: true)); - return true; - } - - bool validatePhone() { - if (state.phone.isEmpty) { - emit(state.copyWith(isPhoneValid: false)); - return false; - } - emit(state.copyWith(isPhoneValid: true)); - return true; - } - - bool validateGender() { - if (state.gender.isEmpty) { - emit(state.copyWith(isGenderValid: false)); - return false; - } - emit(state.copyWith(isGenderValid: true)); - return true; - } - - bool validateWeight() { - if (state.weight == 0) { - emit(state.copyWith(isWeightValid: false)); - return false; - } - emit(state.copyWith(isWeightValid: true)); - return true; - } - - bool validateHeight() { - if (state.height == 0) { - emit(state.copyWith(isHeightValid: false)); - return false; - } - emit(state.copyWith(isHeightValid: true)); - return true; - } - - bool validateActivityCoefficient() { - if (state.activityCoefficient == 0) { - emit(state.copyWith(isActivityCoefficientValid: false)); - return false; - } - emit(state.copyWith(isActivityCoefficientValid: true)); - return true; - } - - bool validateBirthday() { - if (state.birthday == null) { - emit(state.copyWith(isBirthdayValid: false)); - return false; - } - emit(state.copyWith(isBirthdayValid: true)); - return true; - } - - void nextStep() { - emit(state.copyWith(step: 1)); - } - - void prevStep() { - emit(state.copyWith(step: 0)); - } - - bool validateAllFields() { - if (!validateFirstName() || - !validateLastName() || - !validateEmail() || - !validatePhone() || - !validateGender() || - !validateWeight() || - !validateHeight() || - !validateActivityCoefficient() || - !validateBirthday()) { - return false; - } - return true; - } - - Future<(bool, bool)> register() async { - if (!validateAllFields()) { - return (false, true); - } - final birthday = state.birthday?.format('yyyy-MM-dd'); - if (birthday == null) { - validateBirthday(); - return (false, true); - } - final password = await _generatePassword(); - final registrationModel = RegistrationModel( - firstName: state.firstName, - lastName: state.lastName, - email: state.email, - phone: state.phone, - gender: state.gender, - password: password, - confirmPassword: password, - weight: state.weight, - height: state.height, - activityCoefficient: state.activityCoefficient, - birthday: birthday, - ); - bool didApiComplainOnValidation = false; - try { - final token = await registrationRepository.register(registrationModel: registrationModel); - if (token != null) { - TokenStorage.saveTokenInStorage(token); - return (true, false); - } - } on DioException catch (e) { - final errors = e.response?.data['errors'] as Map?; - final emailErrors = errors?['email'] as List?; - final phoneErrors = errors?['phone'] as List?; - final dateOfBirth = errors?['date_of_birth'] as List?; - if (emailErrors != null && emailErrors.isNotEmpty) { - if (emailErrors.contains('Некорректный формат email')) { - emit(state.copyWith(isEmailValid: false, emailDisplayError: 'Некорректный формат email')); - didApiComplainOnValidation = true; - } - if (emailErrors.contains('Такой Эл. почта уже существует.')) { - emit(state.copyWith(isEmailValid: false, emailDisplayError: 'Такая Эл. почта уже существует.')); - didApiComplainOnValidation = true; - } - } - if (phoneErrors != null && phoneErrors.isNotEmpty) { - if (phoneErrors.contains('Такой phone уже существует.')) { - emit(state.copyWith(isPhoneValid: false, phoneDisplayError: 'Такой номер телефона уже существует.')); - didApiComplainOnValidation = true; - } - } - if (dateOfBirth != null && dateOfBirth.isNotEmpty) { - if (dateOfBirth.contains('Некорректный формат даты рождения')) { - emit(state.copyWith(isBirthdayValid: false, emailDisplayError: 'Некорректный формат даты рождения')); - didApiComplainOnValidation = true; - } - } - } catch (e) { - log(e.toString()); - } - return (false, didApiComplainOnValidation); - } - - Future _generatePassword() async { - final generator = GPassword(); - final password = generator.generate(passwordLength: 10); - emit(state.copyWith(password: password)); - return password; - } -} +import 'dart:developer'; + +import 'package:dio/dio.dart'; +import 'package:eatvio_app/core/token_storage.dart'; +import 'package:eatvio_app/core/utils/date_time_utils.dart'; +import 'package:eatvio_app/features/registration_v2/cubit/registration_state.dart'; +import 'package:eatvio_app/features/registration_v2/models/registration_model.dart'; +import 'package:eatvio_app/features/registration_v2/repositories/registration_repository_v2.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:gpassword/gpassword.dart'; + +class RegistrationCubitV2 extends Cubit { + final RegistrationRepositoryV2 registrationRepository; + RegistrationCubitV2({required this.registrationRepository}) : super(const RegistrationStateV2()); + + void setFirstName(String firstName) { + emit(state.copyWith(firstName: firstName)); + validateFirstName(); + } + + void setLastName(String lastName) { + emit(state.copyWith(lastName: lastName)); + validateLastName(); + } + + void setEmail(String email) { + emit(state.copyWith(email: email)); + validateEmail(); + } + + void setPhone(String phone) { + emit(state.copyWith(phone: phone)); + validatePhone(); + } + + void setBirthday(DateTime birthday) { + emit(state.copyWith(birthday: birthday)); + validateBirthday(); + } + + void setGender(String gender) { + emit(state.copyWith(gender: gender)); + validateGender(); + } + + void setWeight(int weight) { + emit(state.copyWith(weight: weight)); + validateWeight(); + } + + void setHeight(int height) { + emit(state.copyWith(height: height)); + validateHeight(); + } + + void setActivityCoefficient(double activityCoefficient) { + emit(state.copyWith(activityCoefficient: activityCoefficient)); + validateActivityCoefficient(); + } + + void setPassword(String password) { + emit(state.copyWith(password: password)); + } + + bool validateFirstName() { + if (state.firstName.isEmpty) { + emit(state.copyWith(isFirstNameValid: false)); + return false; + } + emit(state.copyWith(isFirstNameValid: true)); + return true; + } + + bool validateLastName() { + if (state.lastName.isEmpty) { + emit(state.copyWith(isLastNameValid: false)); + return false; + } + emit(state.copyWith(isLastNameValid: true)); + return true; + } + + bool validateEmail() { + if (state.email.isEmpty) { + emit(state.copyWith(isEmailValid: false)); + return false; + } + emit(state.copyWith(isEmailValid: true)); + return true; + } + + bool validatePhone() { + if (state.phone.isEmpty) { + emit(state.copyWith(isPhoneValid: false)); + return false; + } + emit(state.copyWith(isPhoneValid: true)); + return true; + } + + bool validateGender() { + if (state.gender.isEmpty) { + emit(state.copyWith(isGenderValid: false)); + return false; + } + emit(state.copyWith(isGenderValid: true)); + return true; + } + + bool validateWeight() { + if (state.weight == 0) { + emit(state.copyWith(isWeightValid: false)); + return false; + } + emit(state.copyWith(isWeightValid: true)); + return true; + } + + bool validateHeight() { + if (state.height == 0) { + emit(state.copyWith(isHeightValid: false)); + return false; + } + emit(state.copyWith(isHeightValid: true)); + return true; + } + + bool validateActivityCoefficient() { + if (state.activityCoefficient == 0) { + emit(state.copyWith(isActivityCoefficientValid: false)); + return false; + } + emit(state.copyWith(isActivityCoefficientValid: true)); + return true; + } + + bool validateBirthday() { + if (state.birthday == null) { + emit(state.copyWith(isBirthdayValid: false)); + return false; + } + emit(state.copyWith(isBirthdayValid: true)); + return true; + } + + void nextStep() { + emit(state.copyWith(step: 1)); + } + + void prevStep() { + emit(state.copyWith(step: 0)); + } + + bool validateAllFields() { + if (!validateFirstName() || + !validateLastName() || + !validateEmail() || + !validatePhone() || + !validateGender() || + !validateWeight() || + !validateHeight() || + !validateActivityCoefficient() || + !validateBirthday()) { + return false; + } + return true; + } + + Future<(bool, bool)> register() async { + if (!validateAllFields()) { + return (false, true); + } + final birthday = state.birthday?.format('yyyy-MM-dd'); + if (birthday == null) { + validateBirthday(); + return (false, true); + } + final password = await _generatePassword(); + final registrationModel = RegistrationModel( + firstName: state.firstName, + lastName: state.lastName, + email: state.email, + phone: state.phone, + gender: state.gender, + password: password, + confirmPassword: password, + weight: state.weight, + height: state.height, + activityCoefficient: state.activityCoefficient, + birthday: birthday, + ); + bool didApiComplainOnValidation = false; + String errorMessage = ''; + try { + final token = await registrationRepository.register(registrationModel: registrationModel); + if (token != null) { + TokenStorage.saveTokenInStorage(token); + emit(state.copyWith(registrationError: null)); + return (true, false); + } + } on DioException catch (e) { + final errors = e.response?.data['errors'] as Map?; + final emailErrors = errors?['email'] as List?; + final phoneErrors = errors?['phone'] as List?; + final dateOfBirth = errors?['date_of_birth'] as List?; + + // Собираем все ошибки для отображения в диалоге + List allErrors = []; + + if (emailErrors != null && emailErrors.isNotEmpty) { + for (var error in emailErrors) { + allErrors.add(error.toString()); + if (error.toString() == 'Некорректный формат email') { + emit(state.copyWith(isEmailValid: false, emailDisplayError: 'Некорректный формат email')); + didApiComplainOnValidation = true; + } + if (error.toString() == 'Такой Эл. почта уже существует.') { + emit(state.copyWith(isEmailValid: false, emailDisplayError: 'Такая Эл. почта уже существует.')); + didApiComplainOnValidation = true; + } + } + } + if (phoneErrors != null && phoneErrors.isNotEmpty) { + for (var error in phoneErrors) { + allErrors.add(error.toString()); + if (error.toString() == 'Такой phone уже существует.') { + emit(state.copyWith(isPhoneValid: false, phoneDisplayError: 'Такой номер телефона уже существует.')); + didApiComplainOnValidation = true; + } + } + } + if (dateOfBirth != null && dateOfBirth.isNotEmpty) { + for (var error in dateOfBirth) { + allErrors.add(error.toString()); + if (error.toString() == 'Некорректный формат даты рождения') { + emit(state.copyWith(isBirthdayValid: false, dateOfBirthDisplayError: 'Некорректный формат даты рождения')); + didApiComplainOnValidation = true; + } + } + } + + // Сохраняем все ошибки для отображения в диалоге + if (allErrors.isNotEmpty) { + errorMessage = allErrors.join('\n'); + emit(state.copyWith(registrationError: errorMessage)); + } else { + // Если нет конкретных ошибок, берем общее сообщение + final message = e.response?.data['message'] as String?; + errorMessage = message ?? 'Ошибка при регистрации'; + emit(state.copyWith(registrationError: errorMessage)); + } + } catch (e) { + log(e.toString()); + errorMessage = e.toString(); + emit(state.copyWith(registrationError: errorMessage)); + } + return (false, didApiComplainOnValidation); + } + + Future _generatePassword() async { + final generator = GPassword(); + final password = generator.generate(passwordLength: 10); + emit(state.copyWith(password: password)); + return password; + } +} diff --git a/lib/features/registration_v2/cubit/registration_state.dart b/lib/features/registration_v2/cubit/registration_state.dart index de26c146..8191d7d2 100644 --- a/lib/features/registration_v2/cubit/registration_state.dart +++ b/lib/features/registration_v2/cubit/registration_state.dart @@ -1,32 +1,33 @@ -import 'package:freezed_annotation/freezed_annotation.dart'; - -part 'registration_state.freezed.dart'; - -@freezed -abstract class RegistrationStateV2 with _$RegistrationStateV2 { - factory RegistrationStateV2({ - @Default('') String firstName, - @Default('') String lastName, - @Default('') String email, - @Default('') String phone, - @Default('') String gender, - @Default('') String password, - @Default(0) int weight, - @Default(0) int height, - @Default(1.2) double activityCoefficient, - DateTime? birthday, - @Default(true) bool isFirstNameValid, - @Default(true) bool isLastNameValid, - @Default(true) bool isEmailValid, - @Default(true) bool isPhoneValid, - @Default(true) bool isGenderValid, - @Default(true) bool isWeightValid, - @Default(true) bool isHeightValid, - @Default(true) bool isActivityCoefficientValid, - @Default(true) bool isBirthdayValid, - @Default(0) int step, - @Default('') String emailDisplayError, - @Default('') String phoneDisplayError, - @Default('') String dateOfBirthDisplayError, - }) = _RegistrationStateV2; -} +import 'package:freezed_annotation/freezed_annotation.dart'; + +part 'registration_state.freezed.dart'; + +@freezed +abstract class RegistrationStateV2 with _$RegistrationStateV2 { + const factory RegistrationStateV2({ + @Default('') String firstName, + @Default('') String lastName, + @Default('') String email, + @Default('') String phone, + @Default('') String gender, + @Default('') String password, + @Default(0) int weight, + @Default(0) int height, + @Default(1.2) double activityCoefficient, + DateTime? birthday, + @Default(true) bool isFirstNameValid, + @Default(true) bool isLastNameValid, + @Default(true) bool isEmailValid, + @Default(true) bool isPhoneValid, + @Default(true) bool isGenderValid, + @Default(true) bool isWeightValid, + @Default(true) bool isHeightValid, + @Default(true) bool isActivityCoefficientValid, + @Default(true) bool isBirthdayValid, + @Default(0) int step, + @Default('') String emailDisplayError, + @Default('') String phoneDisplayError, + @Default('') String dateOfBirthDisplayError, + String? registrationError, + }) = _RegistrationStateV2; +} diff --git a/lib/features/registration_v2/presentation/widgets/second_step_content.dart b/lib/features/registration_v2/presentation/widgets/second_step_content.dart index 6980dc0d..c51d1c1d 100644 --- a/lib/features/registration_v2/presentation/widgets/second_step_content.dart +++ b/lib/features/registration_v2/presentation/widgets/second_step_content.dart @@ -131,7 +131,8 @@ class _SecondStepContentState extends State { setState(() { isLoading = false; }); - if (context.mounted && !hasValidationError) { + if (context.mounted) { + // Показываем bottom sheet всегда: при успехе или ошибке showRegistrationBottomSheet(context, cubit, commonResult); } }, diff --git a/lib/features/registration_v2/presentation/widgets/show_successful_registration_bottom_sheet.dart b/lib/features/registration_v2/presentation/widgets/show_successful_registration_bottom_sheet.dart index 7be50ec2..f172935a 100644 --- a/lib/features/registration_v2/presentation/widgets/show_successful_registration_bottom_sheet.dart +++ b/lib/features/registration_v2/presentation/widgets/show_successful_registration_bottom_sheet.dart @@ -1,106 +1,111 @@ -import 'package:eatvio_app/core/app_router.dart'; -import 'package:eatvio_app/core/routing/route_constants.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_outlined_button.dart'; -import 'package:eatvio_app/features/registration_v2/cubit/registration_cubit.dart'; -import 'package:eatvio_app/features/registration_v2/presentation/widgets/password_field.dart'; -import 'package:eatvio_app/generated/l10n.dart'; -import 'package:flutter/material.dart'; -import 'package:flutter_svg/flutter_svg.dart'; -import 'package:go_router/go_router.dart'; - -void showRegistrationBottomSheet(BuildContext context, RegistrationCubitV2 cubit, bool isSuccess) { - showModalBottomSheet( - context: context, - isScrollControlled: true, - backgroundColor: Colors.transparent, - builder: (context) => Container( - margin: EdgeInsets.only(left: 8, right: 8, bottom: MediaQuery.of(context).viewInsets.bottom + MediaQuery.of(context).padding.bottom), - decoration: BoxDecoration(borderRadius: BorderRadius.circular(12), color: appTheme.whiteA700), - child: RegistrationBottomSheet(cubit: cubit, isSuccess: isSuccess), - ), - ); -} - -class RegistrationBottomSheet extends StatefulWidget { - const RegistrationBottomSheet({super.key, required this.cubit, required this.isSuccess}); - final RegistrationCubitV2 cubit; - final bool isSuccess; - @override - State createState() => _RegistrationBottomSheetState(); -} - -class _RegistrationBottomSheetState extends State { - late bool isSuccess; - bool isLoading = false; - @override - void initState() { - super.initState(); - isSuccess = widget.isSuccess; - } - - @override - Widget build(BuildContext context) { - final s = S.of(context); - return Padding( - padding: const EdgeInsets.all(20), - child: Column( - mainAxisSize: MainAxisSize.min, - children: isSuccess - ? [ - Padding( - padding: const EdgeInsets.only(bottom: 12.0), - child: Row( - children: [ - SvgPicture.asset('assets/icons/sucess.svg', height: 16, width: 16), - const SizedBox(width: 8), - Text(s.registration_completed, style: TextStyles.textFS22FW600.copyWith(color: appTheme.black)), - ], - ), - ), - - Padding( - padding: const EdgeInsets.only(bottom: 16.0), - child: Text(s.acc_has_been_created, style: TextStyles.textMMediumAuto.copyWith(color: appTheme.darkGrey)), - ), - PasswordField(cubit: widget.cubit), - Padding( - padding: const EdgeInsets.only(bottom: 20.0), - child: Text(s.password_desc, style: TextStyles.textSMedium.copyWith(color: appTheme.darkGrey)), - ), - - CustomOutlinedButton( - text: s.login, - height: 52, - buttonTextStyle: TextStyles.textMSemibold.copyWith(color: appTheme.whiteA700), - buttonStyle: ButtonStyle(side: const WidgetStatePropertyAll(BorderSide.none), backgroundColor: WidgetStatePropertyAll(appTheme.blueA400)), - onPressed: () async { - AppRouter.navigatorKey.currentContext?.go(RouteConstants.feedScreen); - }, - ), - const SizedBox(width: 20), - //button - ] - : [ - Padding( - padding: const EdgeInsets.only(bottom: 12.0), - child: Row( - children: [ - SvgPicture.asset('assets/icons/close_mark.svg', height: 16, width: 16), - const SizedBox(width: 8), - Text(s.signUpFailed, style: TextStyles.textFS22FW600.copyWith(color: appTheme.black)), - ], - ), - ), - - Padding( - padding: const EdgeInsets.only(bottom: 16.0), - child: Text(s.error_desc, style: TextStyles.textMMediumAuto.copyWith(color: appTheme.darkGrey)), - ), - const SizedBox(width: 20), - ], - ), - ); - } -} +import 'package:eatvio_app/core/app_router.dart'; +import 'package:eatvio_app/core/routing/route_constants.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_outlined_button.dart'; +import 'package:eatvio_app/features/registration_v2/cubit/registration_cubit.dart'; +import 'package:eatvio_app/features/registration_v2/presentation/widgets/password_field.dart'; +import 'package:eatvio_app/generated/l10n.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_svg/flutter_svg.dart'; +import 'package:go_router/go_router.dart'; + +void showRegistrationBottomSheet(BuildContext context, RegistrationCubitV2 cubit, bool isSuccess) { + showModalBottomSheet( + context: context, + isScrollControlled: true, + backgroundColor: Colors.transparent, + builder: (context) => Container( + margin: EdgeInsets.only(left: 8, right: 8, bottom: MediaQuery.of(context).viewInsets.bottom + MediaQuery.of(context).padding.bottom), + decoration: BoxDecoration(borderRadius: BorderRadius.circular(12), color: appTheme.whiteA700), + child: RegistrationBottomSheet(cubit: cubit, isSuccess: isSuccess), + ), + ); +} + +class RegistrationBottomSheet extends StatefulWidget { + const RegistrationBottomSheet({super.key, required this.cubit, required this.isSuccess}); + final RegistrationCubitV2 cubit; + final bool isSuccess; + @override + State createState() => _RegistrationBottomSheetState(); +} + +class _RegistrationBottomSheetState extends State { + late bool isSuccess; + bool isLoading = false; + @override + void initState() { + super.initState(); + isSuccess = widget.isSuccess; + } + + @override + Widget build(BuildContext context) { + final s = S.of(context); + return Padding( + padding: const EdgeInsets.all(20), + child: Column( + mainAxisSize: MainAxisSize.min, + children: isSuccess + ? [ + Padding( + padding: const EdgeInsets.only(bottom: 12.0), + child: Row( + children: [ + SvgPicture.asset('assets/icons/sucess.svg', height: 16, width: 16), + const SizedBox(width: 8), + Text(s.registration_completed, style: TextStyles.textFS22FW600.copyWith(color: appTheme.black)), + ], + ), + ), + + Padding( + padding: const EdgeInsets.only(bottom: 16.0), + child: Text(s.acc_has_been_created, style: TextStyles.textMMediumAuto.copyWith(color: appTheme.darkGrey)), + ), + PasswordField(cubit: widget.cubit), + Padding( + padding: const EdgeInsets.only(bottom: 20.0), + child: Text(s.password_desc, style: TextStyles.textSMedium.copyWith(color: appTheme.darkGrey)), + ), + + CustomOutlinedButton( + text: s.login, + height: 52, + buttonTextStyle: TextStyles.textMSemibold.copyWith(color: appTheme.whiteA700), + buttonStyle: ButtonStyle(side: const WidgetStatePropertyAll(BorderSide.none), backgroundColor: WidgetStatePropertyAll(appTheme.blueA400)), + onPressed: () async { + AppRouter.navigatorKey.currentContext?.go(RouteConstants.feedScreen); + }, + ), + const SizedBox(width: 20), + //button + ] + : [ + Padding( + padding: const EdgeInsets.only(bottom: 12.0), + child: Row( + children: [ + SvgPicture.asset('assets/icons/close_mark.svg', height: 16, width: 16), + const SizedBox(width: 8), + Text(s.signUpFailed, style: TextStyles.textFS22FW600.copyWith(color: appTheme.black)), + ], + ), + ), + + Padding( + padding: const EdgeInsets.only(bottom: 16.0), + child: Text( + widget.cubit.state.registrationError?.isNotEmpty == true + ? widget.cubit.state.registrationError! + : s.error_desc, + style: TextStyles.textMMediumAuto.copyWith(color: appTheme.darkGrey), + ), + ), + const SizedBox(width: 20), + ], + ), + ); + } +} diff --git a/test/core/constants/app_constants_test.dart b/test/core/constants/app_constants_test.dart index 212fd279..d2837a33 100644 --- a/test/core/constants/app_constants_test.dart +++ b/test/core/constants/app_constants_test.dart @@ -151,6 +151,9 @@ void main() { + + + diff --git a/test/core/cubit/state_utils_test.dart b/test/core/cubit/state_utils_test.dart index 7d66f5c4..d02b1966 100644 --- a/test/core/cubit/state_utils_test.dart +++ b/test/core/cubit/state_utils_test.dart @@ -86,6 +86,9 @@ void main() { + + + diff --git a/test/core/data/selection_popup_model_test.dart b/test/core/data/selection_popup_model_test.dart index f136a109..1dfa6539 100644 --- a/test/core/data/selection_popup_model_test.dart +++ b/test/core/data/selection_popup_model_test.dart @@ -100,6 +100,9 @@ void main() { + + + diff --git a/test/core/enums/prefs_keys_test.dart b/test/core/enums/prefs_keys_test.dart index 2f05ac74..bf3b3771 100644 --- a/test/core/enums/prefs_keys_test.dart +++ b/test/core/enums/prefs_keys_test.dart @@ -49,6 +49,9 @@ void main() { + + + diff --git a/test/core/internet_connection_cubit/internet_connection_state_test.dart b/test/core/internet_connection_cubit/internet_connection_state_test.dart index 0d7e4e6a..e3ba98f6 100644 --- a/test/core/internet_connection_cubit/internet_connection_state_test.dart +++ b/test/core/internet_connection_cubit/internet_connection_state_test.dart @@ -75,6 +75,9 @@ void main() { + + + diff --git a/test/core/prefs_keys_test.dart b/test/core/prefs_keys_test.dart index 412178ac..42aa9ddd 100644 --- a/test/core/prefs_keys_test.dart +++ b/test/core/prefs_keys_test.dart @@ -45,6 +45,9 @@ void main() { + + + diff --git a/test/core/routing/main_navigation_routes_test.dart b/test/core/routing/main_navigation_routes_test.dart index 93ef13c0..d312fb8c 100644 --- a/test/core/routing/main_navigation_routes_test.dart +++ b/test/core/routing/main_navigation_routes_test.dart @@ -42,6 +42,9 @@ void main() { + + + diff --git a/test/core/routing/other_routes_test.dart b/test/core/routing/other_routes_test.dart index 23e461ae..11915124 100644 --- a/test/core/routing/other_routes_test.dart +++ b/test/core/routing/other_routes_test.dart @@ -46,6 +46,9 @@ void main() { + + + diff --git a/test/core/routing/route_constants_test.dart b/test/core/routing/route_constants_test.dart index 726a2f37..eab452b0 100644 --- a/test/core/routing/route_constants_test.dart +++ b/test/core/routing/route_constants_test.dart @@ -125,6 +125,9 @@ void main() { + + + diff --git a/test/core/routing/settings_routes_test.dart b/test/core/routing/settings_routes_test.dart index 337495fb..1b3e7e7b 100644 --- a/test/core/routing/settings_routes_test.dart +++ b/test/core/routing/settings_routes_test.dart @@ -49,6 +49,9 @@ void main() { + + + diff --git a/test/core/views/cubit/views_state_test.dart b/test/core/views/cubit/views_state_test.dart index 0271a905..722ea5c6 100644 --- a/test/core/views/cubit/views_state_test.dart +++ b/test/core/views/cubit/views_state_test.dart @@ -43,6 +43,9 @@ void main() { + + + diff --git a/test/core/widgets/add_item_button_test.dart b/test/core/widgets/add_item_button_test.dart index 2eb83315..d793418c 100644 --- a/test/core/widgets/add_item_button_test.dart +++ b/test/core/widgets/add_item_button_test.dart @@ -79,6 +79,9 @@ void main() { + + + diff --git a/test/core/widgets/animated_option_button_test.dart b/test/core/widgets/animated_option_button_test.dart index 64688618..80f93b55 100644 --- a/test/core/widgets/animated_option_button_test.dart +++ b/test/core/widgets/animated_option_button_test.dart @@ -131,6 +131,9 @@ void main() { + + + diff --git a/test/core/widgets/base_button_test.dart b/test/core/widgets/base_button_test.dart index f54cef0f..d975e404 100644 --- a/test/core/widgets/base_button_test.dart +++ b/test/core/widgets/base_button_test.dart @@ -68,6 +68,9 @@ void main() { + + + diff --git a/test/core/widgets/bottom_sheet_with_blur_test.dart b/test/core/widgets/bottom_sheet_with_blur_test.dart index e96d327c..44f78c72 100644 --- a/test/core/widgets/bottom_sheet_with_blur_test.dart +++ b/test/core/widgets/bottom_sheet_with_blur_test.dart @@ -150,6 +150,9 @@ void main() { + + + diff --git a/test/core/widgets/cpfc_container_test.dart b/test/core/widgets/cpfc_container_test.dart index 32ad2f4f..d3dab197 100644 --- a/test/core/widgets/cpfc_container_test.dart +++ b/test/core/widgets/cpfc_container_test.dart @@ -111,6 +111,9 @@ void main() { + + + diff --git a/test/core/widgets/custom_search_bar_test.dart b/test/core/widgets/custom_search_bar_test.dart index f3a2d382..d1a5ef1d 100644 --- a/test/core/widgets/custom_search_bar_test.dart +++ b/test/core/widgets/custom_search_bar_test.dart @@ -179,6 +179,9 @@ void main() { + + + diff --git a/test/core/widgets/item_widget_test.dart b/test/core/widgets/item_widget_test.dart index bc486efc..802eaf19 100644 --- a/test/core/widgets/item_widget_test.dart +++ b/test/core/widgets/item_widget_test.dart @@ -72,6 +72,9 @@ void main() { + + + diff --git a/test/core/widgets/loader_test.dart b/test/core/widgets/loader_test.dart index c20fcbf1..3abbf5ca 100644 --- a/test/core/widgets/loader_test.dart +++ b/test/core/widgets/loader_test.dart @@ -57,6 +57,9 @@ void main() { + + + diff --git a/test/core/widgets/main_background_bouble_test.dart b/test/core/widgets/main_background_bouble_test.dart index 67253d3d..b08b62c5 100644 --- a/test/core/widgets/main_background_bouble_test.dart +++ b/test/core/widgets/main_background_bouble_test.dart @@ -111,6 +111,9 @@ void main() { + + + diff --git a/test/core/widgets/marketing_bottom_sheet_test.dart b/test/core/widgets/marketing_bottom_sheet_test.dart index d68c190d..32e92e7b 100644 --- a/test/core/widgets/marketing_bottom_sheet_test.dart +++ b/test/core/widgets/marketing_bottom_sheet_test.dart @@ -91,6 +91,9 @@ void main() { + + + diff --git a/test/core/widgets/option_widget_test.dart b/test/core/widgets/option_widget_test.dart index 54b7bcc6..cf8a3cf9 100644 --- a/test/core/widgets/option_widget_test.dart +++ b/test/core/widgets/option_widget_test.dart @@ -80,6 +80,9 @@ void main() { + + + diff --git a/test/features/create_diet/presentation/widgets/error_widget_test.dart b/test/features/create_diet/presentation/widgets/error_widget_test.dart index 3859102e..a08332c9 100644 --- a/test/features/create_diet/presentation/widgets/error_widget_test.dart +++ b/test/features/create_diet/presentation/widgets/error_widget_test.dart @@ -86,6 +86,9 @@ void main() { + + + diff --git a/test/features/diary/presentation/widgets/chosen_diet_widget_content_test.dart b/test/features/diary/presentation/widgets/chosen_diet_widget_content_test.dart index f3ac19ba..dbd269f3 100644 --- a/test/features/diary/presentation/widgets/chosen_diet_widget_content_test.dart +++ b/test/features/diary/presentation/widgets/chosen_diet_widget_content_test.dart @@ -95,6 +95,9 @@ void main() { + + + diff --git a/test/features/diary/presentation/widgets/comment_container_test.dart b/test/features/diary/presentation/widgets/comment_container_test.dart index 5316d64c..b71a1fb5 100644 --- a/test/features/diary/presentation/widgets/comment_container_test.dart +++ b/test/features/diary/presentation/widgets/comment_container_test.dart @@ -73,6 +73,9 @@ void main() { + + + diff --git a/test/features/diary/presentation/widgets/finish_day_button_widget_test.dart b/test/features/diary/presentation/widgets/finish_day_button_widget_test.dart index af39ef8f..e5bfa43c 100644 --- a/test/features/diary/presentation/widgets/finish_day_button_widget_test.dart +++ b/test/features/diary/presentation/widgets/finish_day_button_widget_test.dart @@ -68,6 +68,9 @@ void main() { + + + diff --git a/test/features/diary/presentation/widgets/meal_summary_widget_test.dart b/test/features/diary/presentation/widgets/meal_summary_widget_test.dart index 868d865c..8ddb9676 100644 --- a/test/features/diary/presentation/widgets/meal_summary_widget_test.dart +++ b/test/features/diary/presentation/widgets/meal_summary_widget_test.dart @@ -105,6 +105,9 @@ void main() { + + + diff --git a/test/features/diary/presentation/widgets/product_loader_widget_test.dart b/test/features/diary/presentation/widgets/product_loader_widget_test.dart index 14aab087..e8c586f3 100644 --- a/test/features/diary/presentation/widgets/product_loader_widget_test.dart +++ b/test/features/diary/presentation/widgets/product_loader_widget_test.dart @@ -57,6 +57,9 @@ void main() { + + + diff --git a/test/features/diets/presentation/widgets/info_bouble_test.dart b/test/features/diets/presentation/widgets/info_bouble_test.dart index 0423bdb8..fcf5e7d2 100644 --- a/test/features/diets/presentation/widgets/info_bouble_test.dart +++ b/test/features/diets/presentation/widgets/info_bouble_test.dart @@ -123,6 +123,9 @@ void main() { + + + diff --git a/test/features/feed/presentation/widgets/choice_chip_widget_test.dart b/test/features/feed/presentation/widgets/choice_chip_widget_test.dart index 3bc45c42..6fe8c2cf 100644 --- a/test/features/feed/presentation/widgets/choice_chip_widget_test.dart +++ b/test/features/feed/presentation/widgets/choice_chip_widget_test.dart @@ -153,6 +153,9 @@ void main() { + + + diff --git a/test/features/feed/presentation/widgets/filter_badge_test.dart b/test/features/feed/presentation/widgets/filter_badge_test.dart index eaf151ae..56110db0 100644 --- a/test/features/feed/presentation/widgets/filter_badge_test.dart +++ b/test/features/feed/presentation/widgets/filter_badge_test.dart @@ -65,6 +65,9 @@ void main() { + + + diff --git a/test/features/finances_and_consultations_archive/presentation/widgets/status_widget_test.dart b/test/features/finances_and_consultations_archive/presentation/widgets/status_widget_test.dart index 6d03fe81..af8b6c2f 100644 --- a/test/features/finances_and_consultations_archive/presentation/widgets/status_widget_test.dart +++ b/test/features/finances_and_consultations_archive/presentation/widgets/status_widget_test.dart @@ -73,6 +73,9 @@ void main() { + + + diff --git a/test/features/glossary/repositories/glossary_repository_test.dart b/test/features/glossary/repositories/glossary_repository_test.dart index 338d4006..a852e69a 100644 --- a/test/features/glossary/repositories/glossary_repository_test.dart +++ b/test/features/glossary/repositories/glossary_repository_test.dart @@ -66,6 +66,9 @@ void main() { + + + diff --git a/test/features/notifications/presentation/widgets/notification_widget_test.dart b/test/features/notifications/presentation/widgets/notification_widget_test.dart index 8ed9da23..f624993a 100644 --- a/test/features/notifications/presentation/widgets/notification_widget_test.dart +++ b/test/features/notifications/presentation/widgets/notification_widget_test.dart @@ -1043,6 +1043,9 @@ void main() { + + + diff --git a/test/features/notifications/service/notifications_service_test.dart b/test/features/notifications/service/notifications_service_test.dart index 988085d2..2de7ac36 100644 --- a/test/features/notifications/service/notifications_service_test.dart +++ b/test/features/notifications/service/notifications_service_test.dart @@ -77,5 +77,8 @@ void main() { + + + From 4e23992d226224bc87d58c6d68cd941f6b88a1fe Mon Sep 17 00:00:00 2001 From: mso7nueh Date: Fri, 26 Dec 2025 13:37:41 +0300 Subject: [PATCH 2/3] chore: bump version number to 1.3.11+91 in pubspec.yaml --- pubspec.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pubspec.yaml b/pubspec.yaml index b0f1ec1d..d0edec0d 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,6 +1,6 @@ name: eatvio_app description: A new Flutter project. -version: 1.3.10+90 +version: 1.3.11+91 publish_to: 'none' environment: From 90746d0be7c4fd38624f3738d8d5514d0dae0afd Mon Sep 17 00:00:00 2001 From: mso7nueh Date: Fri, 26 Dec 2025 13:54:10 +0300 Subject: [PATCH 3/3] refactor(tests): update password and sex widget tests to use const constructors for RegistrationStateV2 --- .../widgets/password_field_test.dart | 166 ++++++------ .../presentation/widgets/sex_widget_test.dart | 12 +- ...essful_registration_bottom_sheet_test.dart | 236 +++++++++--------- .../widgets/step_widget_test.dart | 8 +- 4 files changed, 211 insertions(+), 211 deletions(-) diff --git a/test/features/registration_v2/presentation/widgets/password_field_test.dart b/test/features/registration_v2/presentation/widgets/password_field_test.dart index 3c3d800d..575a6a2b 100644 --- a/test/features/registration_v2/presentation/widgets/password_field_test.dart +++ b/test/features/registration_v2/presentation/widgets/password_field_test.dart @@ -1,83 +1,83 @@ -import 'package:eatvio_app/core/app_config.dart'; -import 'package:eatvio_app/core/app_initializer.dart'; -import 'package:eatvio_app/core/locator_service.dart'; -import 'package:eatvio_app/core/theme/theme_helper.dart'; -import 'package:eatvio_app/features/registration_v2/cubit/registration_cubit.dart'; -import 'package:eatvio_app/features/registration_v2/cubit/registration_state.dart'; -import 'package:eatvio_app/features/registration_v2/presentation/widgets/password_field.dart'; -import 'package:eatvio_app/generated/l10n.dart'; -import 'package:flutter/material.dart'; -import 'package:flutter_bloc/flutter_bloc.dart'; -import 'package:flutter_localizations/flutter_localizations.dart'; -import 'package:flutter_portal/flutter_portal.dart'; -import 'package:flutter_test/flutter_test.dart'; -import 'package:flutter_dotenv/flutter_dotenv.dart'; -import 'package:mockito/annotations.dart'; -import 'package:mockito/mockito.dart'; - -import 'password_field_test.mocks.dart'; - -@GenerateNiceMocks([MockSpec()]) -void main() { - group('PasswordField', () { - setUp(() async { - await sl.reset(); - dotenv.loadFromString(envString: 'DEV_BASE_URL=http://localhost\nBASE_URL=http://localhost'); - sl.registerLazySingleton(() => AppConfig(buildType: AppBuildType.tests)..setBaseUrl('http://localhost')); - }); - - Widget createTestWidget(Widget child, RegistrationCubitV2 cubit) { - return MaterialApp( - theme: ThemeHelper().themeData(), - localizationsDelegates: const [ - S.delegate, - GlobalMaterialLocalizations.delegate, - GlobalWidgetsLocalizations.delegate, - GlobalCupertinoLocalizations.delegate, - ], - supportedLocales: const [Locale('ru')], - home: Portal( - child: BlocProvider( - create: (_) => cubit, - child: Scaffold(body: child), - ), - ), - ); - } - - testWidgets('should display password field with password from state', (WidgetTester tester) async { - // Arrange - final mockCubit = MockRegistrationCubitV2(); - const testPassword = 'testPassword123'; - when(mockCubit.state).thenReturn( - RegistrationStateV2(password: testPassword), - ); - final widget = PasswordField(cubit: mockCubit); - - // Act - await tester.pumpWidget(createTestWidget(widget, mockCubit)); - await tester.pump(); - - // Assert - expect(find.byType(PasswordField), findsOneWidget); - expect(find.text(testPassword), findsOneWidget); - }); - - testWidgets('should show copy icon initially', (WidgetTester tester) async { - // Arrange - final mockCubit = MockRegistrationCubitV2(); - when(mockCubit.state).thenReturn( - RegistrationStateV2(password: 'test'), - ); - final widget = PasswordField(cubit: mockCubit); - - // Act - await tester.pumpWidget(createTestWidget(widget, mockCubit)); - await tester.pump(); - - // Assert - expect(find.byType(PasswordField), findsOneWidget); - }); - }); -} - +import 'package:eatvio_app/core/app_config.dart'; +import 'package:eatvio_app/core/app_initializer.dart'; +import 'package:eatvio_app/core/locator_service.dart'; +import 'package:eatvio_app/core/theme/theme_helper.dart'; +import 'package:eatvio_app/features/registration_v2/cubit/registration_cubit.dart'; +import 'package:eatvio_app/features/registration_v2/cubit/registration_state.dart'; +import 'package:eatvio_app/features/registration_v2/presentation/widgets/password_field.dart'; +import 'package:eatvio_app/generated/l10n.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:flutter_localizations/flutter_localizations.dart'; +import 'package:flutter_portal/flutter_portal.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:flutter_dotenv/flutter_dotenv.dart'; +import 'package:mockito/annotations.dart'; +import 'package:mockito/mockito.dart'; + +import 'password_field_test.mocks.dart'; + +@GenerateNiceMocks([MockSpec()]) +void main() { + group('PasswordField', () { + setUp(() async { + await sl.reset(); + dotenv.loadFromString(envString: 'DEV_BASE_URL=http://localhost\nBASE_URL=http://localhost'); + sl.registerLazySingleton(() => AppConfig(buildType: AppBuildType.tests)..setBaseUrl('http://localhost')); + }); + + Widget createTestWidget(Widget child, RegistrationCubitV2 cubit) { + return MaterialApp( + theme: ThemeHelper().themeData(), + localizationsDelegates: const [ + S.delegate, + GlobalMaterialLocalizations.delegate, + GlobalWidgetsLocalizations.delegate, + GlobalCupertinoLocalizations.delegate, + ], + supportedLocales: const [Locale('ru')], + home: Portal( + child: BlocProvider( + create: (_) => cubit, + child: Scaffold(body: child), + ), + ), + ); + } + + testWidgets('should display password field with password from state', (WidgetTester tester) async { + // Arrange + final mockCubit = MockRegistrationCubitV2(); + const testPassword = 'testPassword123'; + when(mockCubit.state).thenReturn( + const RegistrationStateV2(password: testPassword), + ); + final widget = PasswordField(cubit: mockCubit); + + // Act + await tester.pumpWidget(createTestWidget(widget, mockCubit)); + await tester.pump(); + + // Assert + expect(find.byType(PasswordField), findsOneWidget); + expect(find.text(testPassword), findsOneWidget); + }); + + testWidgets('should show copy icon initially', (WidgetTester tester) async { + // Arrange + final mockCubit = MockRegistrationCubitV2(); + when(mockCubit.state).thenReturn( + const RegistrationStateV2(password: 'test'), + ); + final widget = PasswordField(cubit: mockCubit); + + // Act + await tester.pumpWidget(createTestWidget(widget, mockCubit)); + await tester.pump(); + + // Assert + expect(find.byType(PasswordField), findsOneWidget); + }); + }); +} + diff --git a/test/features/registration_v2/presentation/widgets/sex_widget_test.dart b/test/features/registration_v2/presentation/widgets/sex_widget_test.dart index 3e5fbae2..0c85cc60 100644 --- a/test/features/registration_v2/presentation/widgets/sex_widget_test.dart +++ b/test/features/registration_v2/presentation/widgets/sex_widget_test.dart @@ -42,14 +42,14 @@ void main() { testWidgets('displays sex widget', (tester) async { when(mockCubit.state).thenReturn( - RegistrationStateV2( + const RegistrationStateV2( step: 0, gender: '', isGenderValid: true, ), ); when(mockCubit.stream).thenAnswer((_) => Stream.value( - RegistrationStateV2( + const RegistrationStateV2( step: 0, gender: '', isGenderValid: true, @@ -64,14 +64,14 @@ void main() { testWidgets('calls setGender when male is tapped', (tester) async { when(mockCubit.state).thenReturn( - RegistrationStateV2( + const RegistrationStateV2( step: 0, gender: '', isGenderValid: true, ), ); when(mockCubit.stream).thenAnswer((_) => Stream.value( - RegistrationStateV2( + const RegistrationStateV2( step: 0, gender: '', isGenderValid: true, @@ -94,14 +94,14 @@ void main() { testWidgets('shows error when gender is invalid', (tester) async { when(mockCubit.state).thenReturn( - RegistrationStateV2( + const RegistrationStateV2( step: 0, gender: '', isGenderValid: false, ), ); when(mockCubit.stream).thenAnswer((_) => Stream.value( - RegistrationStateV2( + const RegistrationStateV2( step: 0, gender: '', isGenderValid: false, diff --git a/test/features/registration_v2/presentation/widgets/show_successful_registration_bottom_sheet_test.dart b/test/features/registration_v2/presentation/widgets/show_successful_registration_bottom_sheet_test.dart index 42ce3534..6a9945f4 100644 --- a/test/features/registration_v2/presentation/widgets/show_successful_registration_bottom_sheet_test.dart +++ b/test/features/registration_v2/presentation/widgets/show_successful_registration_bottom_sheet_test.dart @@ -1,118 +1,118 @@ -import 'package:eatvio_app/core/app_config.dart'; -import 'package:eatvio_app/core/app_initializer.dart'; -import 'package:eatvio_app/core/locator_service.dart'; -import 'package:eatvio_app/core/theme/theme_helper.dart'; -import 'package:eatvio_app/features/registration_v2/cubit/registration_cubit.dart'; -import 'package:eatvio_app/features/registration_v2/cubit/registration_state.dart'; -import 'package:eatvio_app/features/registration_v2/presentation/widgets/show_successful_registration_bottom_sheet.dart'; -import 'package:eatvio_app/generated/l10n.dart'; -import 'package:flutter/material.dart'; -import 'package:flutter_bloc/flutter_bloc.dart'; -import 'package:flutter_localizations/flutter_localizations.dart'; -import 'package:flutter_portal/flutter_portal.dart'; -import 'package:flutter_test/flutter_test.dart'; -import 'package:flutter_dotenv/flutter_dotenv.dart'; -import 'package:mockito/annotations.dart'; -import 'package:mockito/mockito.dart'; - -import 'show_successful_registration_bottom_sheet_test.mocks.dart'; - -@GenerateNiceMocks([MockSpec()]) -void main() { - group('ShowSuccessfulRegistrationBottomSheet', () { - setUp(() async { - await sl.reset(); - dotenv.loadFromString(envString: 'DEV_BASE_URL=http://localhost\nBASE_URL=http://localhost'); - sl.registerLazySingleton(() => AppConfig(buildType: AppBuildType.tests)..setBaseUrl('http://localhost')); - }); - - Widget createTestWidget(Widget child, RegistrationCubitV2 cubit) { - return MaterialApp( - theme: ThemeHelper().themeData(), - localizationsDelegates: const [ - S.delegate, - GlobalMaterialLocalizations.delegate, - GlobalWidgetsLocalizations.delegate, - GlobalCupertinoLocalizations.delegate, - ], - supportedLocales: const [Locale('ru')], - home: Portal( - child: BlocProvider( - create: (_) => cubit, - child: Scaffold( - body: Builder( - builder: (context) => ElevatedButton( - onPressed: () { - showRegistrationBottomSheet(context, cubit, true); - }, - child: const Text('Show Bottom Sheet'), - ), - ), - ), - ), - ), - ); - } - - testWidgets('should display success bottom sheet when isSuccess is true', (WidgetTester tester) async { - // Arrange - final mockCubit = MockRegistrationCubitV2(); - when(mockCubit.state).thenReturn( - RegistrationStateV2(password: 'testPassword'), - ); - - // Act - await tester.pumpWidget(createTestWidget(const SizedBox(), mockCubit)); - await tester.tap(find.text('Show Bottom Sheet')); - await tester.pumpAndSettle(); - - // Assert - expect(find.byType(RegistrationBottomSheet), findsOneWidget); - }); - - testWidgets('should display error bottom sheet when isSuccess is false', (WidgetTester tester) async { - // Arrange - final mockCubit = MockRegistrationCubitV2(); - when(mockCubit.state).thenReturn( - RegistrationStateV2(), - ); - - Widget createTestWidgetError(Widget child, RegistrationCubitV2 cubit) { - return MaterialApp( - theme: ThemeHelper().themeData(), - localizationsDelegates: const [ - S.delegate, - GlobalMaterialLocalizations.delegate, - GlobalWidgetsLocalizations.delegate, - GlobalCupertinoLocalizations.delegate, - ], - supportedLocales: const [Locale('ru')], - home: Portal( - child: BlocProvider( - create: (_) => cubit, - child: Scaffold( - body: Builder( - builder: (context) => ElevatedButton( - onPressed: () { - showRegistrationBottomSheet(context, cubit, false); - }, - child: const Text('Show Bottom Sheet'), - ), - ), - ), - ), - ), - ); - } - - // Act - await tester.pumpWidget(createTestWidgetError(const SizedBox(), mockCubit)); - await tester.tap(find.text('Show Bottom Sheet')); - await tester.pumpAndSettle(); - - // Assert - expect(find.byType(RegistrationBottomSheet), findsOneWidget); - }); - }); -} - +import 'package:eatvio_app/core/app_config.dart'; +import 'package:eatvio_app/core/app_initializer.dart'; +import 'package:eatvio_app/core/locator_service.dart'; +import 'package:eatvio_app/core/theme/theme_helper.dart'; +import 'package:eatvio_app/features/registration_v2/cubit/registration_cubit.dart'; +import 'package:eatvio_app/features/registration_v2/cubit/registration_state.dart'; +import 'package:eatvio_app/features/registration_v2/presentation/widgets/show_successful_registration_bottom_sheet.dart'; +import 'package:eatvio_app/generated/l10n.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:flutter_localizations/flutter_localizations.dart'; +import 'package:flutter_portal/flutter_portal.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:flutter_dotenv/flutter_dotenv.dart'; +import 'package:mockito/annotations.dart'; +import 'package:mockito/mockito.dart'; + +import 'show_successful_registration_bottom_sheet_test.mocks.dart'; + +@GenerateNiceMocks([MockSpec()]) +void main() { + group('ShowSuccessfulRegistrationBottomSheet', () { + setUp(() async { + await sl.reset(); + dotenv.loadFromString(envString: 'DEV_BASE_URL=http://localhost\nBASE_URL=http://localhost'); + sl.registerLazySingleton(() => AppConfig(buildType: AppBuildType.tests)..setBaseUrl('http://localhost')); + }); + + Widget createTestWidget(Widget child, RegistrationCubitV2 cubit) { + return MaterialApp( + theme: ThemeHelper().themeData(), + localizationsDelegates: const [ + S.delegate, + GlobalMaterialLocalizations.delegate, + GlobalWidgetsLocalizations.delegate, + GlobalCupertinoLocalizations.delegate, + ], + supportedLocales: const [Locale('ru')], + home: Portal( + child: BlocProvider( + create: (_) => cubit, + child: Scaffold( + body: Builder( + builder: (context) => ElevatedButton( + onPressed: () { + showRegistrationBottomSheet(context, cubit, true); + }, + child: const Text('Show Bottom Sheet'), + ), + ), + ), + ), + ), + ); + } + + testWidgets('should display success bottom sheet when isSuccess is true', (WidgetTester tester) async { + // Arrange + final mockCubit = MockRegistrationCubitV2(); + when(mockCubit.state).thenReturn( + const RegistrationStateV2(password: 'testPassword'), + ); + + // Act + await tester.pumpWidget(createTestWidget(const SizedBox(), mockCubit)); + await tester.tap(find.text('Show Bottom Sheet')); + await tester.pumpAndSettle(); + + // Assert + expect(find.byType(RegistrationBottomSheet), findsOneWidget); + }); + + testWidgets('should display error bottom sheet when isSuccess is false', (WidgetTester tester) async { + // Arrange + final mockCubit = MockRegistrationCubitV2(); + when(mockCubit.state).thenReturn( + const RegistrationStateV2(), + ); + + Widget createTestWidgetError(Widget child, RegistrationCubitV2 cubit) { + return MaterialApp( + theme: ThemeHelper().themeData(), + localizationsDelegates: const [ + S.delegate, + GlobalMaterialLocalizations.delegate, + GlobalWidgetsLocalizations.delegate, + GlobalCupertinoLocalizations.delegate, + ], + supportedLocales: const [Locale('ru')], + home: Portal( + child: BlocProvider( + create: (_) => cubit, + child: Scaffold( + body: Builder( + builder: (context) => ElevatedButton( + onPressed: () { + showRegistrationBottomSheet(context, cubit, false); + }, + child: const Text('Show Bottom Sheet'), + ), + ), + ), + ), + ), + ); + } + + // Act + await tester.pumpWidget(createTestWidgetError(const SizedBox(), mockCubit)); + await tester.tap(find.text('Show Bottom Sheet')); + await tester.pumpAndSettle(); + + // Assert + expect(find.byType(RegistrationBottomSheet), findsOneWidget); + }); + }); +} + diff --git a/test/features/registration_v2/presentation/widgets/step_widget_test.dart b/test/features/registration_v2/presentation/widgets/step_widget_test.dart index 50279979..70fec32d 100644 --- a/test/features/registration_v2/presentation/widgets/step_widget_test.dart +++ b/test/features/registration_v2/presentation/widgets/step_widget_test.dart @@ -43,9 +43,9 @@ void main() { testWidgets('displays step widget', (tester) async { when(mockCubit.state).thenReturn( - RegistrationStateV2(step: 0), + const RegistrationStateV2(step: 0), ); - when(mockCubit.stream).thenAnswer((_) => Stream.value(RegistrationStateV2(step: 0))); + when(mockCubit.stream).thenAnswer((_) => Stream.value(const RegistrationStateV2(step: 0))); await tester.pumpWidget(createTestWidget()); await tester.pumpAndSettle(); @@ -55,9 +55,9 @@ void main() { testWidgets('displays correct step number', (tester) async { when(mockCubit.state).thenReturn( - RegistrationStateV2(step: 2), + const RegistrationStateV2(step: 2), ); - when(mockCubit.stream).thenAnswer((_) => Stream.value(RegistrationStateV2(step: 2))); + when(mockCubit.stream).thenAnswer((_) => Stream.value(const RegistrationStateV2(step: 2))); await tester.pumpWidget(createTestWidget()); await tester.pumpAndSettle();