From 2440204294c07688bf36632bb057588f96291b89 Mon Sep 17 00:00:00 2001 From: misha Date: Mon, 20 Oct 2025 17:55:56 +0300 Subject: [PATCH] feat(registration): added base layout for new registration page, and a bit of state managment --- assets/icons/sucess.svg | 3 + lib/core/di/cubit_registrations.dart | 2 + lib/core/routing/auth_routes.dart | 2 + lib/core/routing/route_constants.dart | 1 + lib/core/theme/custom_text_style.dart | 233 ++++-------------- lib/core/theme/theme_helper.dart | 40 ++- lib/core/widgets/custom_drop_down_button.dart | 15 +- .../auth/presentation/pages/auth_screen.dart | 2 +- .../cubit/registration_cubit.dart | 117 +++++++++ .../cubit/registration_state.dart | 29 +++ .../models/registration_model.dart | 0 .../pages/registration_v2_page.dart | 47 ++++ .../widgets/first_step_content.dart | 89 +++++++ .../presentation/widgets/password_field.dart | 38 +++ .../widgets/phisical_activity.dart | 83 +++++++ .../widgets/second_step_content.dart | 120 +++++++++ .../presentation/widgets/sex_widget.dart | 84 +++++++ ..._successful_registration_bottom_sheet.dart | 56 +++++ .../presentation/widgets/step_progress.dart | 29 +++ .../presentation/widgets/step_widget.dart | 24 ++ lib/generated/intl/messages_en.dart | 68 ++++- lib/generated/l10n.dart | 192 ++++++++++++++- lib/l10n/intl_en.arb | 24 +- 23 files changed, 1072 insertions(+), 226 deletions(-) create mode 100644 assets/icons/sucess.svg create mode 100644 lib/features/registration_v2/cubit/registration_cubit.dart create mode 100644 lib/features/registration_v2/cubit/registration_state.dart create mode 100644 lib/features/registration_v2/models/registration_model.dart create mode 100644 lib/features/registration_v2/presentation/pages/registration_v2_page.dart create mode 100644 lib/features/registration_v2/presentation/widgets/first_step_content.dart create mode 100644 lib/features/registration_v2/presentation/widgets/password_field.dart create mode 100644 lib/features/registration_v2/presentation/widgets/phisical_activity.dart create mode 100644 lib/features/registration_v2/presentation/widgets/second_step_content.dart create mode 100644 lib/features/registration_v2/presentation/widgets/sex_widget.dart create mode 100644 lib/features/registration_v2/presentation/widgets/show_successful_registration_bottom_sheet.dart create mode 100644 lib/features/registration_v2/presentation/widgets/step_progress.dart create mode 100644 lib/features/registration_v2/presentation/widgets/step_widget.dart diff --git a/assets/icons/sucess.svg b/assets/icons/sucess.svg new file mode 100644 index 00000000..a4cf6d09 --- /dev/null +++ b/assets/icons/sucess.svg @@ -0,0 +1,3 @@ + + + diff --git a/lib/core/di/cubit_registrations.dart b/lib/core/di/cubit_registrations.dart index ca3ece97..36e9823d 100644 --- a/lib/core/di/cubit_registrations.dart +++ b/lib/core/di/cubit_registrations.dart @@ -46,6 +46,7 @@ import 'package:eatvio_app/features/recipe/presentation/cubit/about_recipe_cubit import 'package:eatvio_app/features/recipe/presentation/cubit/my_recipes_cubit/my_recieps_cubit.dart'; import 'package:eatvio_app/features/registration/presentation/bloc/registration_cubit/registration_cubit.dart'; import 'package:eatvio_app/features/registration/presentation/bloc/verify_email_cubit/verify_email_cubit.dart'; +import 'package:eatvio_app/features/registration_v2/cubit/registration_cubit.dart'; import 'package:eatvio_app/features/settings/presentation/bloc/settings_cubit/settings_cubit.dart'; import 'package:eatvio_app/features/user_details/presentation/cubit/user_details_cubit.dart'; import 'package:eatvio_app/core/comments/comments_cubit/comments_cubit.dart'; @@ -78,6 +79,7 @@ class CubitRegistrations { sl.registerFactory(() => ResetPasswordCubit(resetPasswordRepository: sl())); sl.registerFactory(() => VerifyEmailCubit(profileRepository: sl())); sl.registerFactory(() => OAuthCubit(authRepository: sl())); + sl.registerFactory(() => RegistrationCubitV2()); // Feed related cubits sl.registerFactory(() => FeedCubit(feedRepository: sl())); sl.registerFactory(() => RecipesCubit(feedRepository: sl(), recipeRepository: sl(), aboutRecipeRepository: sl())); diff --git a/lib/core/routing/auth_routes.dart b/lib/core/routing/auth_routes.dart index 39e7b2a9..2ac62618 100644 --- a/lib/core/routing/auth_routes.dart +++ b/lib/core/routing/auth_routes.dart @@ -9,6 +9,7 @@ import 'package:eatvio_app/features/auth/presentation/pages/scan_qr_code_screen. import 'package:eatvio_app/features/registration/presentation/pages/registration_screen.dart'; import 'package:eatvio_app/features/registration/presentation/pages/verify_screen.dart'; import 'package:eatvio_app/features/registration/presentation/pages/verify_email_screen.dart'; +import 'package:eatvio_app/features/registration_v2/presentation/pages/registration_v2_page.dart'; import 'package:go_router/go_router.dart'; /// Маршруты для аутентификации @@ -26,6 +27,7 @@ class AuthRoutes { }, routes: [ GoRoute(path: RouteConstants.registrationScreen, name: 'registrationScreen', builder: (context, state) => const RegistrationScreen()), + GoRoute(path: RouteConstants.registrationScreenV2, name: 'registrationScreenV2', builder: (context, state) => const RegistrationV2Page()), GoRoute( path: RouteConstants.forgetPasswordScreen, name: 'forgetPasswordScreen', diff --git a/lib/core/routing/route_constants.dart b/lib/core/routing/route_constants.dart index 1702b425..4ce78f4f 100644 --- a/lib/core/routing/route_constants.dart +++ b/lib/core/routing/route_constants.dart @@ -3,6 +3,7 @@ class RouteConstants { // Auth routes static const String authScreen = '/auth-screen'; static const String registrationScreen = 'registration-screen'; + static const String registrationScreenV2 = 'registration-screen-v2'; static const String forgetPasswordScreen = 'forget-password-screen'; static const String scanQRCodeScreen = 'scan-qr-code-screen'; static const String verifyScreen = '/verify-screen'; diff --git a/lib/core/theme/custom_text_style.dart b/lib/core/theme/custom_text_style.dart index 62544e86..b307ab81 100644 --- a/lib/core/theme/custom_text_style.dart +++ b/lib/core/theme/custom_text_style.dart @@ -10,161 +10,76 @@ class CustomTextStyles { static TextStyle get headlineSmallMulish => theme.textTheme.headlineSmall!.mulish; // Title text style - static TextStyle get titleMediumSegoeUI => theme.textTheme.titleMedium!.segoeUI.copyWith( - fontWeight: FontWeight.w600, - ); + static TextStyle get titleMediumSegoeUI => theme.textTheme.titleMedium!.segoeUI.copyWith(fontWeight: FontWeight.w600); - static TextStyle get bodyMediumBlack900 => theme.textTheme.bodyMedium!.copyWith( - color: appTheme.black900, - ); + static TextStyle get bodyMediumBlack900 => theme.textTheme.bodyMedium!.copyWith(color: appTheme.black900); - static TextStyle get bodyMediumBlack90013 => theme.textTheme.bodyMedium!.copyWith( - color: appTheme.black900, - fontSize: 13, - ); + static TextStyle get bodyMediumBlack90013 => theme.textTheme.bodyMedium!.copyWith(color: appTheme.black900, fontSize: 13); - static TextStyle get bodyMediumBluegray800 => theme.textTheme.bodyMedium!.copyWith( - color: appTheme.black, - ); + static TextStyle get bodyMediumBluegray800 => theme.textTheme.bodyMedium!.copyWith(color: appTheme.black); - static TextStyle get bodyMediumBluegray800_1 => theme.textTheme.bodyMedium!.copyWith( - color: appTheme.black, - ); + static TextStyle get bodyMediumBluegray800_1 => theme.textTheme.bodyMedium!.copyWith(color: appTheme.black); - static TextStyle get bodyMediumBluegray800_2 => theme.textTheme.bodyMedium!.copyWith( - color: appTheme.black, - ); + static TextStyle get bodyMediumBluegray800_2 => theme.textTheme.bodyMedium!.copyWith(color: appTheme.black); - static TextStyle get bodyMediumWhiteA700 => theme.textTheme.bodyMedium!.copyWith( - color: appTheme.whiteA700, - ); + static TextStyle get bodyMediumWhiteA700 => theme.textTheme.bodyMedium!.copyWith(color: appTheme.whiteA700); - static TextStyle get bodySmallMulishBlack900 => theme.textTheme.bodySmall!.mulish.copyWith( - color: appTheme.black900, - fontSize: 12, - ); + static TextStyle get bodySmallMulishBlack900 => theme.textTheme.bodySmall!.mulish.copyWith(color: appTheme.black900, fontSize: 12); - static TextStyle get bodySmallMulishBluegray800 => theme.textTheme.bodySmall!.mulish.copyWith( - color: appTheme.black, - fontSize: 12, - ); + static TextStyle get bodySmallMulishBluegray800 => theme.textTheme.bodySmall!.mulish.copyWith(color: appTheme.black, fontSize: 12); - static TextStyle get bodySmallMulishBluegray80012 => theme.textTheme.bodySmall!.mulish.copyWith( - color: appTheme.black, - fontSize: 12, - ); + static TextStyle get bodySmallMulishBluegray80012 => theme.textTheme.bodySmall!.mulish.copyWith(color: appTheme.black, fontSize: 12); - static TextStyle get bodySmallMulishBluegray80012_1 => theme.textTheme.bodySmall!.mulish.copyWith( - color: appTheme.black, - fontSize: 12, - ); + static TextStyle get bodySmallMulishBluegray80012_1 => theme.textTheme.bodySmall!.mulish.copyWith(color: appTheme.black, fontSize: 12); - static TextStyle get bodySmallMulishBluegray900 => theme.textTheme.bodySmall!.mulish.copyWith( - color: appTheme.blueGray900, - fontSize: 12, - ); + static TextStyle get bodySmallMulishBluegray900 => theme.textTheme.bodySmall!.mulish.copyWith(color: appTheme.blueGray900, fontSize: 12); - static TextStyle get bodySmallMulishGray600 => theme.textTheme.bodySmall!.mulish.copyWith( - color: appTheme.gray600, - ); + static TextStyle get bodySmallMulishGray600 => theme.textTheme.bodySmall!.mulish.copyWith(color: appTheme.gray600); - static TextStyle get bodySmallMulishPrimary => theme.textTheme.bodySmall!.mulish.copyWith( - color: theme.colorScheme.primary, - fontSize: 12, - ); + static TextStyle get bodySmallMulishPrimary => theme.textTheme.bodySmall!.mulish.copyWith(color: theme.colorScheme.primary, fontSize: 12); - static TextStyle get bodySmallSegoeUIBlack900 => theme.textTheme.bodySmall!.segoeUI.copyWith( - color: appTheme.black900, - fontSize: 12, - ); + static TextStyle get bodySmallSegoeUIBlack900 => theme.textTheme.bodySmall!.segoeUI.copyWith(color: appTheme.black900, fontSize: 12); // Label text style - static TextStyle get labelLargeGray60003 => theme.textTheme.labelLarge!.copyWith( - color: appTheme.gray60003, - ); + static TextStyle get labelLargeGray60003 => theme.textTheme.labelLarge!.copyWith(color: appTheme.gray60003); - static TextStyle get labelLargeWhiteA700 => theme.textTheme.labelLarge!.copyWith( - color: appTheme.whiteA700, - ); + static TextStyle get labelLargeWhiteA700 => theme.textTheme.labelLarge!.copyWith(color: appTheme.whiteA700); // Title text style - static TextStyle get titleMedium16 => theme.textTheme.titleMedium!.copyWith( - fontSize: 16, - ); + static TextStyle get titleMedium16 => theme.textTheme.titleMedium!.copyWith(fontSize: 16); - static TextStyle get titleMediumBlack => theme.textTheme.titleMedium!.copyWith( - fontSize: 17, - fontWeight: FontWeight.w900, - ); + static TextStyle get titleMediumBlack => theme.textTheme.titleMedium!.copyWith(fontSize: 17, fontWeight: FontWeight.w900); - static TextStyle get titleMediumBlack900 => theme.textTheme.titleMedium!.copyWith( - color: appTheme.black900, - ); + static TextStyle get titleMediumBlack900 => theme.textTheme.titleMedium!.copyWith(color: appTheme.black900); - static TextStyle get titleMediumSemiBold => theme.textTheme.titleMedium!.copyWith( - fontWeight: FontWeight.w600, - ); + static TextStyle get titleMediumSemiBold => theme.textTheme.titleMedium!.copyWith(fontWeight: FontWeight.w600); - static TextStyle get titleMediumWhiteA700 => theme.textTheme.titleMedium!.copyWith( - color: appTheme.whiteA700, - fontWeight: FontWeight.w600, - ); + static TextStyle get titleMediumWhiteA700 => theme.textTheme.titleMedium!.copyWith(color: appTheme.whiteA700, fontWeight: FontWeight.w600); - static TextStyle get titleSmallBlue800 => theme.textTheme.titleSmall!.copyWith( - color: appTheme.blue800, - fontWeight: FontWeight.w800, - ); + static TextStyle get titleSmallBlue800 => theme.textTheme.titleSmall!.copyWith(color: appTheme.blue800, fontWeight: FontWeight.w800); - static TextStyle get titleSmallBluegray800 => theme.textTheme.titleSmall!.copyWith( - color: appTheme.black, - ); + static TextStyle get titleSmallBluegray800 => theme.textTheme.titleSmall!.copyWith(color: appTheme.black); - static TextStyle get titleSmallBluegray800Bold => theme.textTheme.titleSmall!.copyWith( - color: appTheme.black, - fontWeight: FontWeight.w700, - ); + static TextStyle get titleSmallBluegray800Bold => theme.textTheme.titleSmall!.copyWith(color: appTheme.black, fontWeight: FontWeight.w700); - static TextStyle get titleSmallBluegray800ExtraBold => theme.textTheme.titleSmall!.copyWith( - color: appTheme.black, - fontWeight: FontWeight.w800, - ); + static TextStyle get titleSmallBluegray800ExtraBold => theme.textTheme.titleSmall!.copyWith(color: appTheme.black, fontWeight: FontWeight.w800); - static TextStyle get titleSmallBold => theme.textTheme.titleSmall!.copyWith( - fontSize: 15, - fontWeight: FontWeight.w700, - ); + static TextStyle get titleSmallBold => theme.textTheme.titleSmall!.copyWith(fontSize: 15, fontWeight: FontWeight.w700); - static TextStyle get titleSmallGreenA70001 => theme.textTheme.titleSmall!.copyWith( - color: appTheme.greenA70001, - ); + static TextStyle get titleSmallGreenA70001 => theme.textTheme.titleSmall!.copyWith(color: appTheme.greenA70001); - static TextStyle get titleSmallGreenA70001ExtraBold => theme.textTheme.titleSmall!.copyWith( - color: appTheme.greenA70001, - fontWeight: FontWeight.w800, - ); + static TextStyle get titleSmallGreenA70001ExtraBold => theme.textTheme.titleSmall!.copyWith(color: appTheme.greenA70001, fontWeight: FontWeight.w800); - static TextStyle get titleSmallLightgreen300 => theme.textTheme.titleSmall!.copyWith( - color: appTheme.lightGreen300, - fontWeight: FontWeight.w800, - ); + static TextStyle get titleSmallLightgreen300 => theme.textTheme.titleSmall!.copyWith(color: appTheme.lightGreen300, fontWeight: FontWeight.w800); - static TextStyle get titleSmallRed200 => theme.textTheme.titleSmall!.copyWith( - color: appTheme.red200, - fontWeight: FontWeight.w800, - ); + static TextStyle get titleSmallRed200 => theme.textTheme.titleSmall!.copyWith(color: appTheme.red200, fontWeight: FontWeight.w800); - static TextStyle get titleSmallSecondaryContainer => theme.textTheme.titleSmall!.copyWith( - color: theme.colorScheme.secondaryContainer, - fontWeight: FontWeight.w800, - ); + static TextStyle get titleSmallSecondaryContainer => + theme.textTheme.titleSmall!.copyWith(color: theme.colorScheme.secondaryContainer, fontWeight: FontWeight.w800); - static TextStyle get titleSmallSecondaryContainer_1 => theme.textTheme.titleSmall!.copyWith( - color: theme.colorScheme.secondaryContainer, - ); + static TextStyle get titleSmallSecondaryContainer_1 => theme.textTheme.titleSmall!.copyWith(color: theme.colorScheme.secondaryContainer); - static TextStyle get titleSmallSegoeUIOnPrimary => theme.textTheme.titleSmall!.segoeUI.copyWith( - color: theme.colorScheme.onPrimary, - ); + static TextStyle get titleSmallSegoeUIOnPrimary => theme.textTheme.titleSmall!.segoeUI.copyWith(color: theme.colorScheme.onPrimary); } extension on TextStyle { @@ -178,75 +93,31 @@ extension on TextStyle { } abstract class TextStyles { - static const headingXLSemibold = TextStyle( - fontSize: 20, - fontWeight: FontWeight.w600, - height: 1.255, - ); + static const headingXLSemibold = TextStyle(fontSize: 20, fontWeight: FontWeight.w600, height: 1.255); - static const headingFS40FW600 = TextStyle( - fontSize: 40, - fontWeight: FontWeight.w600, - height: 1.255, - ); + static const headingFS40FW600 = TextStyle(fontSize: 40, fontWeight: FontWeight.w600, height: 1.255); - static const textLSemibold = TextStyle( - fontSize: 18, - fontWeight: FontWeight.w600, - height: 1.255, - ); + static const textLSemibold = TextStyle(fontSize: 18, fontWeight: FontWeight.w600, height: 1.255); - static const textLMedium = TextStyle( - fontSize: 18, - fontWeight: FontWeight.w500, - height: 1.255, - ); - - static const textMSemibold = TextStyle( - fontSize: 16, - fontWeight: FontWeight.w600, - height: 1.255, - ); + static const textLMedium = TextStyle(fontSize: 18, fontWeight: FontWeight.w500, height: 1.255); - static const textMMedium = TextStyle( - fontSize: 16, - fontWeight: FontWeight.w500, - height: 1, - ); + static const textMSemibold = TextStyle(fontSize: 16, fontWeight: FontWeight.w600, height: 1.255); - static const textMMediumAuto = TextStyle( - fontSize: 16, - fontWeight: FontWeight.w500, - height: 1.255, - ); + static const textMMedium = TextStyle(fontSize: 16, fontWeight: FontWeight.w500, height: 1); - static const textSSemibold = TextStyle( - fontSize: 14, - fontWeight: FontWeight.w600, - height: 1.255, - ); + static const textMMediumAuto = TextStyle(fontSize: 16, fontWeight: FontWeight.w500, height: 1.255); - static const textSMedium = TextStyle( - fontSize: 14, - fontWeight: FontWeight.w500, - height: 1.255, - ); + static const textSSemibold = TextStyle(fontSize: 14, fontWeight: FontWeight.w600, height: 1.255); - static const textFS13FW400 = TextStyle( - fontSize: 14, - fontWeight: FontWeight.w400, - height: 1.255, - ); + static const textSMedium = TextStyle(fontSize: 14, fontWeight: FontWeight.w500, height: 1.255); - static const textXSSemibold = TextStyle( - fontSize: 12, - fontWeight: FontWeight.w500, - height: 1.255, - ); + static const textFS13FW400 = TextStyle(fontSize: 14, fontWeight: FontWeight.w400, height: 1.255); - static const textFS10FW500 = TextStyle( - fontSize: 10, - fontWeight: FontWeight.w500, - height: 1.255, - ); + static const textXSSemibold = TextStyle(fontSize: 12, fontWeight: FontWeight.w500, height: 1.255); + + static const textFS10FW500 = TextStyle(fontSize: 10, fontWeight: FontWeight.w500, height: 1.255); + + static const textFS24FW700 = TextStyle(fontSize: 24, fontWeight: FontWeight.w700, height: 1.255); + + static const textFS22FW600 = TextStyle(fontSize: 22, fontWeight: FontWeight.w600, height: 1.255); } diff --git a/lib/core/theme/theme_helper.dart b/lib/core/theme/theme_helper.dart index c202129e..421247b6 100644 --- a/lib/core/theme/theme_helper.dart +++ b/lib/core/theme/theme_helper.dart @@ -7,23 +7,17 @@ class ThemeHelper { // The current app theme final _appTheme = 'primary'; -// A map of custom color themes supported by the app - final Map _supportedCustomColor = { - 'primary': PrimaryColors(), - }; + // A map of custom color themes supported by the app + final Map _supportedCustomColor = {'primary': PrimaryColors()}; -// A map of color schemes supported by the app - final Map _supportedColorScheme = { - 'primary': ColorSchemes.primaryColorScheme, - }; + // A map of color schemes supported by the app + final Map _supportedColorScheme = {'primary': ColorSchemes.primaryColorScheme}; /// Returns the primary colors for the current theme. PrimaryColors _getThemeColors() { //throw exception to notify given theme is not found or not generated by the generator if (!_supportedCustomColor.containsKey(_appTheme)) { - throw Exception( - "$_appTheme is not found.Make sure you have added this theme class in JSON Try running flutter pub run build_runner", - ); + throw Exception("$_appTheme is not found.Make sure you have added this theme class in JSON Try running flutter pub run build_runner"); } //return theme from map @@ -34,9 +28,7 @@ class ThemeHelper { ThemeData _getThemeData() { //throw exception to notify given theme is not found or not generated by the generator if (!_supportedColorScheme.containsKey(_appTheme)) { - throw Exception( - "$_appTheme is not found.Make sure you have added this theme class in JSON Try running flutter pub run build_runner", - ); + throw Exception("$_appTheme is not found.Make sure you have added this theme class in JSON Try running flutter pub run build_runner"); } //return theme from map @@ -60,17 +52,9 @@ class ThemeHelper { outlinedButtonTheme: OutlinedButtonThemeData( style: OutlinedButton.styleFrom( backgroundColor: Colors.transparent, - side: BorderSide( - color: colorScheme.primary, - width: 1, - ), - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(20), - ), - visualDensity: const VisualDensity( - vertical: -4, - horizontal: -4, - ), + side: BorderSide(color: colorScheme.primary, width: 1), + shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(20)), + visualDensity: const VisualDensity(vertical: -4, horizontal: -4), padding: EdgeInsets.zero, ), ), @@ -101,7 +85,7 @@ class PrimaryColors { // Blue Color get blue400 => const Color(0XFF43A6F2); - Color get blueA400 => const Color(0XFF2F80ED); + Color get blueA400 => const Color(0XFF2F80ED); // BlueGray Color get blueGray400 => const Color(0XFF7B8794); @@ -179,6 +163,8 @@ class PrimaryColors { Color get transparentBlue => const Color(0xFF2F80ED).withValues(alpha: 0.1); Color get lightGray0 => const Color(0xFFF6F6F6); + + Color get darkGrey => const Color(0xFF5B656F); Color get lightGray00 => const Color(0xFFE9E9EA); @@ -206,6 +192,8 @@ class PrimaryColors { Color get cardNotificationsBG => const Color(0xFFA4A4A4); Color get grey7B => const Color(0xFF7B7B7B); + + Color get greyD9 => const Color(0xFFD9D9D9); } PrimaryColors get appTheme => ThemeHelper().themeColor(); diff --git a/lib/core/widgets/custom_drop_down_button.dart b/lib/core/widgets/custom_drop_down_button.dart index ac8f3c2b..d4c0e571 100644 --- a/lib/core/widgets/custom_drop_down_button.dart +++ b/lib/core/widgets/custom_drop_down_button.dart @@ -3,16 +3,28 @@ import 'package:flutter/material.dart'; import 'package:flutter_svg/svg.dart'; class CustomDropDownButton extends StatelessWidget { - const CustomDropDownButton({super.key, required this.items, required this.onChanged, this.value, this.hint, this.hasError = false}); + const CustomDropDownButton({ + super.key, + required this.items, + required this.onChanged, + this.value, + this.hint, + this.selectedItemBuilder, + this.hasError = false, + this.isExpanded = false, + }); final T? value; final List> items; final void Function(T?)? onChanged; final Widget? hint; final bool hasError; + final List Function(BuildContext)? selectedItemBuilder; + final bool isExpanded; @override Widget build(BuildContext context) { return DropdownButtonFormField( hint: hint, + isExpanded: isExpanded, icon: SvgPicture.asset('assets/icons/arrow_down2.svg', width: 12, height: 8), borderRadius: BorderRadius.circular(20), dropdownColor: appTheme.lightGray0, @@ -35,6 +47,7 @@ class CustomDropDownButton extends StatelessWidget { initialValue: value, items: items, onChanged: onChanged, + selectedItemBuilder: selectedItemBuilder, ); } } diff --git a/lib/features/auth/presentation/pages/auth_screen.dart b/lib/features/auth/presentation/pages/auth_screen.dart index f7484a87..2a63de39 100644 --- a/lib/features/auth/presentation/pages/auth_screen.dart +++ b/lib/features/auth/presentation/pages/auth_screen.dart @@ -337,7 +337,7 @@ class _AuthScreenState extends State { children: [ const Text('Нет аккаунта? '), GestureDetector( - onTap: () => GoRouter.of(context).goNamed('registrationScreen'), + onTap: () => GoRouter.of(context).pushNamed('registrationScreenV2'), child: Text( 'Зарегистрироваться', style: TextStyle(color: appTheme.blueA400, decorationColor: appTheme.blueA400, decoration: TextDecoration.underline), diff --git a/lib/features/registration_v2/cubit/registration_cubit.dart b/lib/features/registration_v2/cubit/registration_cubit.dart new file mode 100644 index 00000000..cb67bdf7 --- /dev/null +++ b/lib/features/registration_v2/cubit/registration_cubit.dart @@ -0,0 +1,117 @@ +import 'package:eatvio_app/features/registration_v2/cubit/registration_state.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; + +class RegistrationCubitV2 extends Cubit { + RegistrationCubitV2() : 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)); + } + + void validateFirstName() { + if (state.firstName.isEmpty) { + emit(state.copyWith(isFirstNameValid: false)); + } + } + + void validateLastName() { + if (state.lastName.isEmpty) { + emit(state.copyWith(isLastNameValid: false)); + } + } + + void validateEmail() { + if (state.email.isEmpty) { + emit(state.copyWith(isEmailValid: false)); + } + } + + void validatePhone() { + if (state.phone.isEmpty) { + emit(state.copyWith(isPhoneValid: false)); + } + } + + void validateGender() { + if (state.gender.isEmpty) { + emit(state.copyWith(isGenderValid: false)); + } + } + + void validateWeight() { + if (state.weight == 0) { + emit(state.copyWith(isWeightValid: false)); + } + } + + void validateHeight() { + if (state.height == 0) { + emit(state.copyWith(isHeightValid: false)); + } + } + + void validateActivityCoefficient() { + if (state.activityCoefficient == 0) { + emit(state.copyWith(isActivityCoefficientValid: false)); + } + } + + void validateBirthday() { + if (state.birthday == null) { + emit(state.copyWith(isBirthdayValid: false)); + } + } + + void nextStep() { + emit(state.copyWith(step: 1)); + } + + void prevStep() { + emit(state.copyWith(step: 0)); + } +} diff --git a/lib/features/registration_v2/cubit/registration_state.dart b/lib/features/registration_v2/cubit/registration_state.dart new file mode 100644 index 00000000..c91eb042 --- /dev/null +++ b/lib/features/registration_v2/cubit/registration_state.dart @@ -0,0 +1,29 @@ +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, + }) = _RegistrationStateV2; +} diff --git a/lib/features/registration_v2/models/registration_model.dart b/lib/features/registration_v2/models/registration_model.dart new file mode 100644 index 00000000..e69de29b diff --git a/lib/features/registration_v2/presentation/pages/registration_v2_page.dart b/lib/features/registration_v2/presentation/pages/registration_v2_page.dart new file mode 100644 index 00000000..b9805d32 --- /dev/null +++ b/lib/features/registration_v2/presentation/pages/registration_v2_page.dart @@ -0,0 +1,47 @@ +import 'package:eatvio_app/core/locator_service.dart'; +import 'package:eatvio_app/core/utils/image_constant.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/first_step_content.dart'; +import 'package:eatvio_app/features/registration_v2/presentation/widgets/second_step_content.dart'; +import 'package:eatvio_app/features/registration_v2/presentation/widgets/step_progress.dart'; +import 'package:eatvio_app/features/registration_v2/presentation/widgets/step_widget.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:flutter_svg/svg.dart'; +import 'package:mask_text_input_formatter/mask_text_input_formatter.dart'; + +class RegistrationV2Page extends StatelessWidget { + const RegistrationV2Page({super.key}); + + @override + Widget build(BuildContext context) { + return BlocProvider(create: (context) => sl(), child: RegistrationV2PageContent()); + } +} + +class RegistrationV2PageContent extends StatelessWidget { + RegistrationV2PageContent({super.key}); + final maskFormatterForBirthday = MaskTextInputFormatter(mask: '##.##.####', filter: {"#": RegExp(r'[0-9]')}, type: MaskAutoCompletionType.lazy); + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar(centerTitle: true, toolbarHeight: 45, title: SvgPicture.asset(ImageConstant.imgLogoEatvio, height: 45), actions: const [StepWidget()]), + body: Padding( + padding: const EdgeInsets.symmetric(horizontal: 16), + child: BlocBuilder( + builder: (context, state) { + return Column( + children: [ + const SizedBox(height: 4), + const StepProgress(), + const SizedBox(height: 20), + if (state.step == 0) Expanded(child: FirstStepContent()) else if (state.step == 1) const Expanded(child: SecondStepContent()), + ], + ); + }, + ), + ), + ); + } +} diff --git a/lib/features/registration_v2/presentation/widgets/first_step_content.dart b/lib/features/registration_v2/presentation/widgets/first_step_content.dart new file mode 100644 index 00000000..d87499c8 --- /dev/null +++ b/lib/features/registration_v2/presentation/widgets/first_step_content.dart @@ -0,0 +1,89 @@ +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/core/widgets/custom_text_form_field.dart'; +import 'package:eatvio_app/features/registration_v2/cubit/registration_cubit.dart'; +import 'package:eatvio_app/features/registration_v2/presentation/widgets/sex_widget.dart'; +import 'package:eatvio_app/generated/l10n.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 FirstStepContent extends StatelessWidget { + FirstStepContent({super.key}); + final maskFormatterForBirthday = MaskTextInputFormatter(mask: '##.##.####', filter: {"#": RegExp(r'[0-9]')}, type: MaskAutoCompletionType.lazy); + @override + Widget build(BuildContext context) { + final s = S.of(context); + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text(s.first_name, style: TextStyles.textSMedium.copyWith(color: appTheme.black)), + const SizedBox(height: 4), + CustomTextFormField(hintText: s.your_name), + ], + ), + ), + const SizedBox(width: 12), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text(s.last_name, style: TextStyles.textSMedium.copyWith(color: appTheme.black)), + const SizedBox(height: 4), + CustomTextFormField(hintText: s.your_last_name), + ], + ), + ), + ], + ), + const SizedBox(height: 12), + Text(s.email, style: TextStyles.textSMedium.copyWith(color: appTheme.black)), + CustomTextFormField(hintText: s.type_email), + const SizedBox(height: 12), + Text(s.phone, style: TextStyles.textSMedium.copyWith(color: appTheme.black)), + const CustomTextFormField(hintText: '+7 (999) 999 99 99'), + const SizedBox(height: 12), + Row( + crossAxisAlignment: CrossAxisAlignment.end, + children: [ + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text(s.date_of_birth, style: TextStyles.textSMedium.copyWith(color: appTheme.black)), + const SizedBox(height: 4), + CustomTextFormField( + textInputAction: TextInputAction.done, + hintText: '01.01.2000', + keyboardType: TextInputType.number, + inputFormatters: [maskFormatterForBirthday], + ), + ], + ), + ), + const SizedBox(width: 12), + const Expanded(child: SexWidget()), + ], + ), + const Spacer(), + CustomOutlinedButton( + onPressed: () { + context.read().nextStep(); + }, + text: s.next, + height: 52, + buttonTextStyle: TextStyles.textSSemibold.copyWith(color: appTheme.whiteA700), + buttonStyle: ButtonStyle(side: const WidgetStatePropertyAll(BorderSide.none), backgroundColor: WidgetStatePropertyAll(appTheme.blueA400)), + ), + const SizedBox(height: 20), + ], + ); + } +} diff --git a/lib/features/registration_v2/presentation/widgets/password_field.dart b/lib/features/registration_v2/presentation/widgets/password_field.dart new file mode 100644 index 00000000..cb0a7a0a --- /dev/null +++ b/lib/features/registration_v2/presentation/widgets/password_field.dart @@ -0,0 +1,38 @@ +import 'package:eatvio_app/core/theme/custom_text_style.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:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; + +class PasswordField extends StatefulWidget { + const PasswordField({super.key, required this.cubit}); + final RegistrationCubitV2 cubit; + @override + State createState() => _PasswordFieldState(); +} + +class _PasswordFieldState extends State { + @override + Widget build(BuildContext context) { + return BlocBuilder( + bloc: widget.cubit, + builder: (context, state) { + return Column( + children: [ + Container( + decoration: BoxDecoration(color: appTheme.lightGray0, borderRadius: BorderRadius.circular(20)), + padding: const EdgeInsets.only(left: 24, right: 16, top: 16, bottom: 16), + width: double.infinity, + child: Row( + children: [ + Text('Klknasd978!9', style: TextStyles.textMMediumAuto.copyWith(color: appTheme.black)), + ], + ), + ), + ], + ); + }, + ); + } +} diff --git a/lib/features/registration_v2/presentation/widgets/phisical_activity.dart b/lib/features/registration_v2/presentation/widgets/phisical_activity.dart new file mode 100644 index 00000000..59e65948 --- /dev/null +++ b/lib/features/registration_v2/presentation/widgets/phisical_activity.dart @@ -0,0 +1,83 @@ +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_drop_down_button.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/generated/l10n.dart'; +import 'package:flutter/material.dart'; +import 'package:collection/collection.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; + +class PhisicalActivity extends StatefulWidget { + const PhisicalActivity({super.key}); + + @override + State createState() => _PhisicalActivityState(); +} + +class _PhisicalActivityState extends State { + final List activityDropDownTitles = []; + + @override + void didChangeDependencies() { + super.didChangeDependencies(); + final s = S.of(context); + if (activityDropDownTitles.isEmpty) { + activityDropDownTitles.add(s.activity_first_value); + activityDropDownTitles.add(s.activity_second_value); + activityDropDownTitles.add(s.activity_third_value); + activityDropDownTitles.add(s.activity_fourth_value); + activityDropDownTitles.add(s.activity_fifth_value); + } + } + + String _getTitleFromValue(double value) { + switch (value) { + case 1.2: + return activityDropDownTitles[0]; + case 1.375: + return activityDropDownTitles[1]; + case 1.55: + return activityDropDownTitles[2]; + case 1.7: + return activityDropDownTitles[3]; + case 1.9: + return activityDropDownTitles[4]; + default: + return activityDropDownTitles[5]; + } + } + + @override + Widget build(BuildContext context) { + return BlocBuilder( + builder: (context, state) { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + CustomDropDownButton( + // hasError: state.dietDurationError != null, + isExpanded: true, + onChanged: (value) { + if (value != null) {} + }, + items: activityDropDownTitles + .mapIndexed( + (index, value) => DropdownMenuItem( + value: activityDropDownTitles[index], + child: Text(activityDropDownTitles[index], style: TextStyles.textMMedium.copyWith(color: appTheme.black)), + ), + ) + .toList(), + value: state.activityCoefficient == 0 ? null : _getTitleFromValue(state.activityCoefficient), + selectedItemBuilder: (context) { + return activityDropDownTitles.mapIndexed((index, value) => Text(value, overflow: TextOverflow.clip, maxLines: 1)).toList(); + }, + ), + // if (state.dietDurationError != null) ...[const SizedBox(height: 4), ValidationErrorWidget(error: state.dietDurationError ?? '')], + ], + ); + }, + ); + } +} diff --git a/lib/features/registration_v2/presentation/widgets/second_step_content.dart b/lib/features/registration_v2/presentation/widgets/second_step_content.dart new file mode 100644 index 00000000..d20de147 --- /dev/null +++ b/lib/features/registration_v2/presentation/widgets/second_step_content.dart @@ -0,0 +1,120 @@ +import 'package:eatvio_app/core/app_config.dart'; +import 'package:eatvio_app/core/const.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_outlined_button.dart'; +import 'package:eatvio_app/core/widgets/custom_text_form_field.dart'; +import 'package:eatvio_app/core/widgets/show_error_dialog.dart'; +import 'package:eatvio_app/features/registration_v2/cubit/registration_cubit.dart'; +import 'package:eatvio_app/features/registration_v2/presentation/widgets/phisical_activity.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/gestures.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; + +import 'package:url_launcher/url_launcher.dart'; + +class SecondStepContent extends StatelessWidget { + const SecondStepContent({super.key}); + @override + Widget build(BuildContext context) { + final s = S.of(context); + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text(s.last_step, style: TextStyles.textFS24FW700.copyWith(color: appTheme.black)), + const SizedBox(height: 12), + Text(s.user_data_desc, style: TextStyles.textSMedium.copyWith(color: appTheme.black)), + const SizedBox(height: 16), + Row( + children: [ + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text(s.height, style: TextStyles.textSMedium.copyWith(color: appTheme.black)), + const SizedBox(height: 4), + CustomTextFormField(hintText: s.type_height), + ], + ), + ), + const SizedBox(width: 12), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text(s.weight, style: TextStyles.textSMedium.copyWith(color: appTheme.black)), + const SizedBox(height: 4), + CustomTextFormField(hintText: s.type_weight), + ], + ), + ), + ], + ), + const SizedBox(height: 12), + Text(s.email, style: TextStyles.textSMedium.copyWith(color: appTheme.black)), + const PhisicalActivity(), + const SizedBox(height: 12), + const Spacer(), + Row( + children: [ + InkWell( + onTap: () { + context.read().prevStep(); + }, + borderRadius: BorderRadius.circular(20), + child: Ink( + height: 52, + width: 52, + decoration: BoxDecoration(color: appTheme.gray50, borderRadius: BorderRadius.circular(20)), + child: Center(child: Icon(Icons.arrow_back_ios_new_rounded, color: appTheme.blueA400, size: 24)), + ), + ), + const SizedBox(width: 4), + Expanded( + child: CustomOutlinedButton( + onPressed: () { + showSuccessfulRegistrationBottomSheet(context, context.read()); + // showErrorDialog(context); + }, + text: s.register, + height: 52, + buttonTextStyle: TextStyles.textSSemibold.copyWith(color: appTheme.whiteA700), + buttonStyle: ButtonStyle(side: const WidgetStatePropertyAll(BorderSide.none), backgroundColor: WidgetStatePropertyAll(appTheme.blueA400)), + ), + ), + ], + ), + const SizedBox(height: 12), + Center( + child: RichText( + textAlign: TextAlign.center, + text: TextSpan( + text: 'Нажимая “Зарегистрироваться”, Вы соглашаетесь \n', + style: const TextStyle(fontSize: 12, color: Colors.black), + children: [ + const TextSpan(text: 'c '), + if (!hidePlans) ...[ + TextSpan( + text: ' Публичной офертой', + style: TextStyle(fontSize: 12, color: appTheme.blueA400, decorationColor: appTheme.blueA400, decoration: TextDecoration.underline), + recognizer: TapGestureRecognizer()..onTap = () async => await launchUrl(Uri.parse('${sl().baseUrl}/oferta_bs.pdf')), + ), + const TextSpan(text: ' и '), + ], + TextSpan( + text: 'Политикой конфиденциальности\nи защитой персональных данных', + style: TextStyle(fontSize: 12, color: appTheme.blueA400, decorationColor: appTheme.blueA400, decoration: TextDecoration.underline), + recognizer: TapGestureRecognizer()..onTap = () async => await launchUrl(Uri.parse('${sl().baseUrl}/docs/privacy.pdf')), + ), + ], + ), + ), + ), + const SizedBox(height: 30), + ], + ); + } +} diff --git a/lib/features/registration_v2/presentation/widgets/sex_widget.dart b/lib/features/registration_v2/presentation/widgets/sex_widget.dart new file mode 100644 index 00000000..ccddcb17 --- /dev/null +++ b/lib/features/registration_v2/presentation/widgets/sex_widget.dart @@ -0,0 +1,84 @@ +import 'package:eatvio_app/core/theme/custom_text_style.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/generated/l10n.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; + +class SexWidget extends StatelessWidget { + const SexWidget({super.key}); + + @override + Widget build(BuildContext context) { + final s = S.of(context); + return BlocBuilder( + builder: (context, state) { + return Row( + crossAxisAlignment: CrossAxisAlignment.end, + children: [ + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text(s.sex, style: TextStyles.textSMedium.copyWith(color: appTheme.black)), + InkWell( + onTap: () { + context.read().setGender('male'); + }, + borderRadius: BorderRadius.circular(20), + child: Ink( + height: 52, + decoration: BoxDecoration( + color: const Color(0xFFF6F6F6), + borderRadius: BorderRadius.circular(20), + border: state.gender == 'male' ? Border.all(width: 2, color: appTheme.blueA400) : null, + ), + child: Center( + child: Text( + s.male_short, + style: TextStyle( + color: state.gender == 'male' ? appTheme.blueA400 : const Color(0xFF323F4B), + fontSize: 16, + fontWeight: FontWeight.w500, + ), + ), + ), + ), + ), + ], + ), + ), + const SizedBox(width: 4), + Expanded( + child: InkWell( + onTap: () { + context.read().setGender('female'); + }, + borderRadius: BorderRadius.circular(20), + child: Ink( + height: 52, + decoration: BoxDecoration( + color: const Color(0xFFF6F6F6), + borderRadius: BorderRadius.circular(20), + border: state.gender == 'female' ? Border.all(width: 2, color: appTheme.blueA400) : null, + ), + child: Center( + child: Text( + s.female_short, + style: TextStyle( + color: state.gender == 'female' ? appTheme.blueA400 : const Color(0xFF323F4B), + fontSize: 16, + fontWeight: FontWeight.w500, + ), + ), + ), + ), + ), + ), + ], + ); + }, + ); + } +} 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 new file mode 100644 index 00000000..132a0b33 --- /dev/null +++ b/lib/features/registration_v2/presentation/widgets/show_successful_registration_bottom_sheet.dart @@ -0,0 +1,56 @@ +import 'package:eatvio_app/core/theme/custom_text_style.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/presentation/widgets/password_field.dart'; +import 'package:eatvio_app/generated/l10n.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_svg/flutter_svg.dart'; + +void showSuccessfulRegistrationBottomSheet(BuildContext context, RegistrationCubitV2 cubit) { + 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: SuccessfulBottomSheet(cubit: cubit), + ), + ); +} + +class SuccessfulBottomSheet extends StatelessWidget { + const SuccessfulBottomSheet({super.key, required this.cubit}); + final RegistrationCubitV2 cubit; + @override + Widget build(BuildContext context) { + final s = S.of(context); + return Padding( + padding: const EdgeInsets.all(20), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + 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: cubit), + Text(s.password_desc, style: TextStyles.textSMedium.copyWith(color: appTheme.darkGrey)), + const SizedBox(width: 20), + //button + ], + ), + ); + } +} diff --git a/lib/features/registration_v2/presentation/widgets/step_progress.dart b/lib/features/registration_v2/presentation/widgets/step_progress.dart new file mode 100644 index 00000000..96d796ad --- /dev/null +++ b/lib/features/registration_v2/presentation/widgets/step_progress.dart @@ -0,0 +1,29 @@ +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:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; + +class StepProgress extends StatelessWidget { + const StepProgress({super.key}); + + @override + Widget build(BuildContext context) { + return BlocBuilder( + builder: (context, state) { + return GestureDetector( + onTap: () { + context.read().nextStep(); + }, + child: LinearProgressIndicator( + value: state.step == 0 ? 0.5 : 1, + borderRadius: BorderRadius.circular(6), + color: appTheme.blueA400, + backgroundColor: appTheme.greyD9, + minHeight: 8, + ), + ); + }, + ); + } +} diff --git a/lib/features/registration_v2/presentation/widgets/step_widget.dart b/lib/features/registration_v2/presentation/widgets/step_widget.dart new file mode 100644 index 00000000..1176213a --- /dev/null +++ b/lib/features/registration_v2/presentation/widgets/step_widget.dart @@ -0,0 +1,24 @@ +import 'package:eatvio_app/core/theme/custom_text_style.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/generated/l10n.dart'; +import 'package:flutter/widgets.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; + +class StepWidget extends StatelessWidget { + const StepWidget({super.key}); + + @override + Widget build(BuildContext context) { + final s = S.of(context); + return BlocBuilder( + builder: (context, state) { + return Padding( + padding: const EdgeInsets.only(right: 16.0), + child: Text(s.step_widget(state.step + 1), style: TextStyles.textMMedium.copyWith(color: appTheme.black)), + ); + }, + ); + } +} diff --git a/lib/generated/intl/messages_en.dart b/lib/generated/intl/messages_en.dart index dc9e4c16..ed7ea1b8 100644 --- a/lib/generated/intl/messages_en.dart +++ b/lib/generated/intl/messages_en.dart @@ -27,22 +27,24 @@ class MessageLookup extends MessageLookupByLibrary { static String m2(planName, name) => "План «${planName}» с пользователем ${name} начался"; - static String m3(planName, name) => + static String m3(step) => "шаг ${step} из 2"; + + static String m4(planName, name) => "Завтра начинается план «${planName}» с пользователем ${name}"; - static String m4(planName) => + static String m5(planName) => "Завтра начнется прохождение плана «${planName}»"; - static String m5(name, planName) => + static String m6(name, planName) => "Пользователь ${name} отменил подписку на план «${planName}»"; - static String m6(name, planName) => + static String m7(name, planName) => "Пользователь ${name} оплатил план «${planName}»"; - static String m7(name, planName) => + static String m8(name, planName) => "Пользователь ${name} подписался на план «${planName}»"; - static String m8(planName) => "Вы отменили подписку на план «${planName}»"; + static String m9(planName) => "Вы отменили подписку на план «${planName}»"; final messages = _notInlinedMessages(_notInlinedMessages); static Map _notInlinedMessages(_) => { @@ -51,6 +53,12 @@ class MessageLookup extends MessageLookupByLibrary { "about_diet": MessageLookupByLibrary.simpleMessage("Об этой диете"), "about_plan": MessageLookupByLibrary.simpleMessage("Об этом плане"), "about_recipe": MessageLookupByLibrary.simpleMessage("Об этом рецепте"), + "acc_has_been_created": MessageLookupByLibrary.simpleMessage( + "Ваш аккаунт создан. Для входа используйте этот пароль:", + ), + "acc_has_been_created_btn": MessageLookupByLibrary.simpleMessage( + "Войти в аккаунт", + ), "accountDeleting": MessageLookupByLibrary.simpleMessage( "Удаление аккаунта", ), @@ -59,6 +67,21 @@ class MessageLookup extends MessageLookupByLibrary { "Кол-во активных консультаций", ), "active_diet_state": MessageLookupByLibrary.simpleMessage("Активная"), + "activity_fifth_value": MessageLookupByLibrary.simpleMessage( + "1,9 — экстремальный (несколько интенсивных тренировок в день)", + ), + "activity_first_value": MessageLookupByLibrary.simpleMessage( + "1,2 — минимальный (сидячая работа, отсутствие физических нагрузок)", + ), + "activity_fourth_value": MessageLookupByLibrary.simpleMessage( + "1,7 — высокий (тренировки 30–60 мин 5–7 раза в неделю; тяжёлая физическая работа)", + ), + "activity_second_value": MessageLookupByLibrary.simpleMessage( + "1,375 — низкий (тренировки не менее 20 мин 1–3 раза в неделю)", + ), + "activity_third_value": MessageLookupByLibrary.simpleMessage( + "1,55 — умеренный (тренировки 30–60 мин 3–4 раза в неделю)", + ), "add": MessageLookupByLibrary.simpleMessage("Добавить"), "addAtLeastOneItem": MessageLookupByLibrary.simpleMessage( "Добавьте хотя бы одину опцию", @@ -353,6 +376,7 @@ class MessageLookup extends MessageLookupByLibrary { "Ваши любимые/нелюбимые продукты питания?", ), "female": MessageLookupByLibrary.simpleMessage("Женский"), + "female_short": MessageLookupByLibrary.simpleMessage("Ж"), "fillYasterday": MessageLookupByLibrary.simpleMessage( "Заполните вчерашний день", ), @@ -433,6 +457,7 @@ class MessageLookup extends MessageLookupByLibrary { "last_name_short": MessageLookupByLibrary.simpleMessage( "В фамилии должен быть минимум 6 символов", ), + "last_step": MessageLookupByLibrary.simpleMessage("Последний шаг"), "lawViolation": MessageLookupByLibrary.simpleMessage( "Нарушение законодательства", ), @@ -454,6 +479,7 @@ class MessageLookup extends MessageLookupByLibrary { "Почта успешно скопирована", ), "male": MessageLookupByLibrary.simpleMessage("Мужской"), + "male_short": MessageLookupByLibrary.simpleMessage("М"), "markAsRead": MessageLookupByLibrary.simpleMessage( "Отметить всё прочитанным", ), @@ -505,6 +531,7 @@ class MessageLookup extends MessageLookupByLibrary { "В имени пользователя должно быть минимум 6 символов", ), "new_card": MessageLookupByLibrary.simpleMessage("Добавить карту"), + "next": MessageLookupByLibrary.simpleMessage("Далее"), "nicotine": MessageLookupByLibrary.simpleMessage("Никотин"), "no": MessageLookupByLibrary.simpleMessage("Нет"), "no_account": MessageLookupByLibrary.simpleMessage("У меня нет аккаунта"), @@ -549,6 +576,9 @@ class MessageLookup extends MessageLookupByLibrary { "password_confirmation_mismatch": MessageLookupByLibrary.simpleMessage( "Пароли не совпадают", ), + "password_desc": MessageLookupByLibrary.simpleMessage( + "Сохраните его в надежном месте! Вы сможете изменить пароль в настройках профиля", + ), "password_empty": MessageLookupByLibrary.simpleMessage( "Пароль обязателен для заполнения", ), @@ -561,6 +591,7 @@ class MessageLookup extends MessageLookupByLibrary { "pending_payment": MessageLookupByLibrary.simpleMessage("Ожидается оплата"), "personal": MessageLookupByLibrary.simpleMessage("Персональные"), "personal_diet_state": MessageLookupByLibrary.simpleMessage("Персональная"), + "phone": MessageLookupByLibrary.simpleMessage("Телефон"), "photo": MessageLookupByLibrary.simpleMessage("Фото"), "physical_fitness": MessageLookupByLibrary.simpleMessage( "Физическая форма", @@ -634,6 +665,10 @@ class MessageLookup extends MessageLookupByLibrary { "refuse_order": MessageLookupByLibrary.simpleMessage( "Отказаться от заказа", ), + "register": MessageLookupByLibrary.simpleMessage("Зарегистрироваться"), + "registration_completed": MessageLookupByLibrary.simpleMessage( + "Регистрация завершена!", + ), "removeFromActive": MessageLookupByLibrary.simpleMessage( "Снять с публикации", ), @@ -686,6 +721,7 @@ class MessageLookup extends MessageLookupByLibrary { "step_by_step_photo_recipe": MessageLookupByLibrary.simpleMessage( "Пошаговый фоторецепт", ), + "step_widget": m3, "successfully_published": MessageLookupByLibrary.simpleMessage( "успешно опубликован", ), @@ -709,8 +745,8 @@ class MessageLookup extends MessageLookupByLibrary { "toOffer": MessageLookupByLibrary.simpleMessage("Предложить"), "to_draft": MessageLookupByLibrary.simpleMessage("В черновик"), "to_order": MessageLookupByLibrary.simpleMessage("Заказать"), - "tommorowPlanWithUserWillStart": m3, - "tomorrowYourPlanWillStart": m4, + "tommorowPlanWithUserWillStart": m4, + "tomorrowYourPlanWillStart": m5, "try_again": MessageLookupByLibrary.simpleMessage("Попробовать снова"), "try_again_2": MessageLookupByLibrary.simpleMessage("Попробуйте ещё раз"), "typeAmoutOfReturn": MessageLookupByLibrary.simpleMessage( @@ -725,9 +761,12 @@ class MessageLookup extends MessageLookupByLibrary { "typePortionComment": MessageLookupByLibrary.simpleMessage( "Прокомментируйте блюдо", ), + "type_email": MessageLookupByLibrary.simpleMessage("Введите email"), + "type_height": MessageLookupByLibrary.simpleMessage("Введите рост"), "type_here_your_complaint": MessageLookupByLibrary.simpleMessage( "Напишите здесь свою жалобу", ), + "type_weight": MessageLookupByLibrary.simpleMessage("Введите вес"), "unfavorite_foods": MessageLookupByLibrary.simpleMessage( "Нелюбимые продукты", ), @@ -735,7 +774,7 @@ class MessageLookup extends MessageLookupByLibrary { "Верхние показатели давления:", ), "upper": MessageLookupByLibrary.simpleMessage("Верхнее"), - "userCanceldThePlanSubscription": m5, + "userCanceldThePlanSubscription": m6, "userConfirmedPlanCompletion": MessageLookupByLibrary.simpleMessage( "Пользователь подтвердил прохождения плана. В ближайшее время Вы получите", ), @@ -743,12 +782,15 @@ class MessageLookup extends MessageLookupByLibrary { MessageLookupByLibrary.simpleMessage( "Пользователь подтвердил прохождения плана.", ), - "userPaidThePlan": m6, - "userSubscribedToPlan": m7, + "userPaidThePlan": m7, + "userSubscribedToPlan": m8, "user_authorization": MessageLookupByLibrary.simpleMessage( "Аутентификация", ), "user_data": MessageLookupByLibrary.simpleMessage("Данные пользователя"), + "user_data_desc": MessageLookupByLibrary.simpleMessage( + "Эти данные нужны для корректной работы дневника питания и расчета вашей дневной нормы потребления КБЖУ", + ), "user_info": MessageLookupByLibrary.simpleMessage("Анкета пользователя"), "user_registration": MessageLookupByLibrary.simpleMessage( "Регистрация пользователя", @@ -794,7 +836,7 @@ class MessageLookup extends MessageLookupByLibrary { "youCanComplain": MessageLookupByLibrary.simpleMessage( "Если вас что-то не устроило, вы можете подать жалобу", ), - "youCanceledSubscriptionToPlan": m8, + "youCanceledSubscriptionToPlan": m9, "youShureYouWantToCancelOrder": MessageLookupByLibrary.simpleMessage( "Вы уверены, что хотите отменить заказ?", ), @@ -817,6 +859,8 @@ class MessageLookup extends MessageLookupByLibrary { ), "your_gender": MessageLookupByLibrary.simpleMessage("Ваш пол?"), "your_height": MessageLookupByLibrary.simpleMessage("Ваш рост:"), + "your_last_name": MessageLookupByLibrary.simpleMessage("Ваша фамилия"), + "your_name": MessageLookupByLibrary.simpleMessage("Ваше имя"), "your_rating": MessageLookupByLibrary.simpleMessage("Ваш рейтинг"), "your_schedule": MessageLookupByLibrary.simpleMessage("Ваш режим дня?"), "your_weight": MessageLookupByLibrary.simpleMessage("Ваш вес:"), diff --git a/lib/generated/l10n.dart b/lib/generated/l10n.dart index 1055e664..4c5fd8fb 100644 --- a/lib/generated/l10n.dart +++ b/lib/generated/l10n.dart @@ -28,10 +28,9 @@ class S { static const AppLocalizationDelegate delegate = AppLocalizationDelegate(); static Future load(Locale locale) { - final name = - (locale.countryCode?.isEmpty ?? false) - ? locale.languageCode - : locale.toString(); + final name = (locale.countryCode?.isEmpty ?? false) + ? locale.languageCode + : locale.toString(); final localeName = Intl.canonicalizedLocale(name); return initializeMessages(localeName).then((_) { Intl.defaultLocale = localeName; @@ -3499,6 +3498,191 @@ class S { args: [], ); } + + /// `шаг {step} из 2` + String step_widget(Object step) { + return Intl.message( + 'шаг $step из 2', + name: 'step_widget', + desc: '', + args: [step], + ); + } + + /// `Ваше имя` + String get your_name { + return Intl.message('Ваше имя', name: 'your_name', desc: '', args: []); + } + + /// `Ваша фамилия` + String get your_last_name { + return Intl.message( + 'Ваша фамилия', + name: 'your_last_name', + desc: '', + args: [], + ); + } + + /// `Введите email` + String get type_email { + return Intl.message( + 'Введите email', + name: 'type_email', + desc: '', + args: [], + ); + } + + /// `Телефон` + String get phone { + return Intl.message('Телефон', name: 'phone', desc: '', args: []); + } + + /// `М` + String get male_short { + return Intl.message('М', name: 'male_short', desc: '', args: []); + } + + /// `Ж` + String get female_short { + return Intl.message('Ж', name: 'female_short', desc: '', args: []); + } + + /// `Далее` + String get next { + return Intl.message('Далее', name: 'next', desc: '', args: []); + } + + /// `Введите рост` + String get type_height { + return Intl.message( + 'Введите рост', + name: 'type_height', + desc: '', + args: [], + ); + } + + /// `Введите вес` + String get type_weight { + return Intl.message('Введите вес', name: 'type_weight', desc: '', args: []); + } + + /// `Зарегистрироваться` + String get register { + return Intl.message( + 'Зарегистрироваться', + name: 'register', + desc: '', + args: [], + ); + } + + /// `Последний шаг` + String get last_step { + return Intl.message('Последний шаг', name: 'last_step', desc: '', args: []); + } + + /// `Эти данные нужны для корректной работы дневника питания и расчета вашей дневной нормы потребления КБЖУ` + String get user_data_desc { + return Intl.message( + 'Эти данные нужны для корректной работы дневника питания и расчета вашей дневной нормы потребления КБЖУ', + name: 'user_data_desc', + desc: '', + args: [], + ); + } + + /// `1,2 — минимальный (сидячая работа, отсутствие физических нагрузок)` + String get activity_first_value { + return Intl.message( + '1,2 — минимальный (сидячая работа, отсутствие физических нагрузок)', + name: 'activity_first_value', + desc: '', + args: [], + ); + } + + /// `1,375 — низкий (тренировки не менее 20 мин 1–3 раза в неделю)` + String get activity_second_value { + return Intl.message( + '1,375 — низкий (тренировки не менее 20 мин 1–3 раза в неделю)', + name: 'activity_second_value', + desc: '', + args: [], + ); + } + + /// `1,55 — умеренный (тренировки 30–60 мин 3–4 раза в неделю)` + String get activity_third_value { + return Intl.message( + '1,55 — умеренный (тренировки 30–60 мин 3–4 раза в неделю)', + name: 'activity_third_value', + desc: '', + args: [], + ); + } + + /// `1,7 — высокий (тренировки 30–60 мин 5–7 раза в неделю; тяжёлая физическая работа)` + String get activity_fourth_value { + return Intl.message( + '1,7 — высокий (тренировки 30–60 мин 5–7 раза в неделю; тяжёлая физическая работа)', + name: 'activity_fourth_value', + desc: '', + args: [], + ); + } + + /// `1,9 — экстремальный (несколько интенсивных тренировок в день)` + String get activity_fifth_value { + return Intl.message( + '1,9 — экстремальный (несколько интенсивных тренировок в день)', + name: 'activity_fifth_value', + desc: '', + args: [], + ); + } + + /// `Регистрация завершена!` + String get registration_completed { + return Intl.message( + 'Регистрация завершена!', + name: 'registration_completed', + desc: '', + args: [], + ); + } + + /// `Ваш аккаунт создан. Для входа используйте этот пароль:` + String get acc_has_been_created { + return Intl.message( + 'Ваш аккаунт создан. Для входа используйте этот пароль:', + name: 'acc_has_been_created', + desc: '', + args: [], + ); + } + + /// `Сохраните его в надежном месте! Вы сможете изменить пароль в настройках профиля` + String get password_desc { + return Intl.message( + 'Сохраните его в надежном месте! Вы сможете изменить пароль в настройках профиля', + name: 'password_desc', + desc: '', + args: [], + ); + } + + /// `Войти в аккаунт` + String get acc_has_been_created_btn { + return Intl.message( + 'Войти в аккаунт', + name: 'acc_has_been_created_btn', + desc: '', + args: [], + ); + } } class AppLocalizationDelegate extends LocalizationsDelegate { diff --git a/lib/l10n/intl_en.arb b/lib/l10n/intl_en.arb index 1fc575a8..6c9b22d2 100644 --- a/lib/l10n/intl_en.arb +++ b/lib/l10n/intl_en.arb @@ -414,6 +414,28 @@ "delete": "Удалить", "cannotDeleteAccount": "Не получилось удалить ваш аккаунт", "resend": "Отправить повторно", - "notifications": "Уведомления" + "notifications": "Уведомления", + "step_widget": "шаг {step} из 2", + "your_name": "Ваше имя", + "your_last_name": "Ваша фамилия", + "type_email": "Введите email", + "phone": "Телефон", + "male_short": "М", + "female_short": "Ж", + "next": "Далее", + "type_height": "Введите рост", + "type_weight": "Введите вес", + "register": "Зарегистрироваться", + "last_step": "Последний шаг", + "user_data_desc": "Эти данные нужны для корректной работы дневника питания и расчета вашей дневной нормы потребления КБЖУ", + "activity_first_value": "1,2 — минимальный (сидячая работа, отсутствие физических нагрузок)", + "activity_second_value": "1,375 — низкий (тренировки не менее 20 мин 1–3 раза в неделю)", + "activity_third_value": "1,55 — умеренный (тренировки 30–60 мин 3–4 раза в неделю)", + "activity_fourth_value": "1,7 — высокий (тренировки 30–60 мин 5–7 раза в неделю; тяжёлая физическая работа)", + "activity_fifth_value": "1,9 — экстремальный (несколько интенсивных тренировок в день)", + "registration_completed": "Регистрация завершена!", + "acc_has_been_created": "Ваш аккаунт создан. Для входа используйте этот пароль:", + "password_desc": "Сохраните его в надежном месте! Вы сможете изменить пароль в настройках профиля", + "acc_has_been_created_btn": "Войти в аккаунт" }