diff --git a/assets/icons/check.svg b/assets/icons/check.svg
new file mode 100644
index 00000000..d0d70964
--- /dev/null
+++ b/assets/icons/check.svg
@@ -0,0 +1,3 @@
+
diff --git a/assets/icons/check_filled.svg b/assets/icons/check_filled.svg
new file mode 100644
index 00000000..2366f097
--- /dev/null
+++ b/assets/icons/check_filled.svg
@@ -0,0 +1,11 @@
+
diff --git a/assets/icons/close_mark.svg b/assets/icons/close_mark.svg
new file mode 100644
index 00000000..dad9d60c
--- /dev/null
+++ b/assets/icons/close_mark.svg
@@ -0,0 +1,12 @@
+
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 a52d8e84..e01596f8 100644
--- a/lib/core/di/cubit_registrations.dart
+++ b/lib/core/di/cubit_registrations.dart
@@ -48,6 +48,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';
@@ -80,6 +81,7 @@ class CubitRegistrations {
sl.registerFactory(() => ResetPasswordCubit(resetPasswordRepository: sl()));
sl.registerFactory(() => VerifyEmailCubit(profileRepository: sl()));
sl.registerFactory(() => OAuthCubit(authRepository: sl(), prefs: sl()));
+ sl.registerFactory(() => RegistrationCubitV2(registrationRepository: sl()));
// Feed related cubits
sl.registerFactory(() => FeedCubit(feedRepository: sl()));
sl.registerFactory(() => RecipesCubit(feedRepository: sl(), recipeRepository: sl(), aboutRecipeRepository: sl()));
diff --git a/lib/core/di/repository_registrations.dart b/lib/core/di/repository_registrations.dart
index dbfd1622..fbbb4498 100644
--- a/lib/core/di/repository_registrations.dart
+++ b/lib/core/di/repository_registrations.dart
@@ -30,6 +30,7 @@ import 'package:eatvio_app/features/profile/repositories/profile_repository.dart
import 'package:eatvio_app/features/recipe/repositories/about_recipe_repository.dart';
import 'package:eatvio_app/features/recipe/repositories/my_recipes_repository.dart';
import 'package:eatvio_app/features/registration/repositories/registration_repository.dart';
+import 'package:eatvio_app/features/registration_v2/repositories/registration_repository_v2.dart';
import 'package:eatvio_app/features/settings/repositories/service_repository.dart';
import 'package:get_it/get_it.dart';
@@ -70,6 +71,7 @@ class RepositoryRegistrations {
sl.registerLazySingleton(() => CommentsRepository(service: sl()));
sl.registerLazySingleton(() => NotificationSettingsRepository(sl()));
sl.registerLazySingleton(() => PersonalDietSuggestionRepository(personalDietSuggestionService: sl()));
+ sl.registerLazySingleton(() => RegistrationRepositoryV2(registrationService: sl()));
// Content repositories
sl.registerLazySingleton(() => BlogPostRepository(blogPostService: sl()));
diff --git a/lib/core/di/service_registrations.dart b/lib/core/di/service_registrations.dart
index 7e14b3a7..f70e28ce 100644
--- a/lib/core/di/service_registrations.dart
+++ b/lib/core/di/service_registrations.dart
@@ -32,6 +32,7 @@ import 'package:eatvio_app/features/profile/services/profile_service.dart';
import 'package:eatvio_app/features/recipe/services/about_recipe_service.dart';
import 'package:eatvio_app/features/recipe/services/my_recipes_service.dart';
import 'package:eatvio_app/features/registration/services/registration_service.dart';
+import 'package:eatvio_app/features/registration_v2/services/registration_service_v2.dart';
import 'package:eatvio_app/features/settings/services/settings_service.dart';
import 'package:get_it/get_it.dart';
@@ -78,6 +79,7 @@ class ServiceRegistrations {
sl.registerLazySingleton(() => NotificationSettingsService(sl()));
sl.registerLazySingleton(() => ViewsService(sl()));
sl.registerLazySingleton(() => PersonalDietSuggestionService(sl()));
+ sl.registerLazySingleton(() => RegistrationServiceV2(sl()));
// Content services
sl.registerLazySingleton(() => CreateRecipeService(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 1c065748..f0d0661b 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,81 +93,32 @@ 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);
- static const textFS24FW700 = TextStyle(
- fontSize: 24,
- fontWeight: FontWeight.w700,
- height: 1.255,
- );
}
diff --git a/lib/core/theme/theme_helper.dart b/lib/core/theme/theme_helper.dart
index 75462df3..01f9a96b 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,
),
),
@@ -149,6 +133,7 @@ class PrimaryColors {
Color get greenA70001 => const Color(0XFF00BE13);
Color get green => const Color(0XFF0BB450);
+ Color get darkGreen => const Color(0XFF16A249);
Color get transparentGreen => const Color.fromARGB(12, 1, 154, 63);
@@ -184,6 +169,8 @@ class PrimaryColors {
Color get lightGray0 => const Color(0xFFF6F6F6);
+ Color get darkGrey => const Color(0xFF5B656F);
+
Color get lightGray00 => const Color(0xFFE9E9EA);
Color get dottedBorder => const Color(0xFFE6E6E6);
@@ -210,6 +197,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..0220c9fb 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),
@@ -411,7 +411,7 @@ class _AuthScreenState extends State {
_isLoading = false;
});
},
- label: state is AuthStateLoading
+ label: state is AuthStateLoading || _isLoading
? const CupertinoActivityIndicator()
: Text('Войти', style: theme.textTheme.titleSmall!.copyWith(color: Colors.white)),
),
@@ -426,8 +426,8 @@ class _AuthScreenState extends State {
elevation: 0,
focusElevation: 0,
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)),
- backgroundColor: state is AuthStateLoading ? Colors.black.withAlpha(50) : appTheme.blueA400,
- onPressed: state is AuthStateLoading
+ backgroundColor: state is AuthStateLoading || _isLoading ? Colors.black.withAlpha(50) : appTheme.blueA400,
+ onPressed: state is AuthStateLoading || _isLoading
? null
: () async {
await showDialog(
@@ -487,7 +487,7 @@ class _AuthScreenState extends State {
},
);
},
- label: state is AuthStateLoading
+ label: state is AuthStateLoading || _isLoading
? const CupertinoActivityIndicator()
: const Icon(Icons.qr_code_scanner_rounded, color: Colors.white),
),
diff --git a/lib/features/blog_post_create/presentation/pages/blog_post_create_screen.dart b/lib/features/blog_post_create/presentation/pages/blog_post_create_screen.dart
index 48536468..98cb0053 100644
--- a/lib/features/blog_post_create/presentation/pages/blog_post_create_screen.dart
+++ b/lib/features/blog_post_create/presentation/pages/blog_post_create_screen.dart
@@ -5,6 +5,7 @@ import 'package:eatvio_app/core/app_config.dart';
import 'package:eatvio_app/core/app_router.dart';
import 'package:eatvio_app/core/permission_handler/permission_handler.dart';
import 'package:eatvio_app/core/theme/app_decoration.dart';
+import 'package:eatvio_app/core/widgets/loader.dart';
import 'package:eatvio_app/core/widgets/show_error_dialog.dart';
import 'package:eatvio_app/features/blog_post_create/bloc/blog_post_create_cubit/blog_post_create_cubit.dart';
import 'package:eatvio_app/features/blog_post_create/bloc/blog_post_create_cubit/blog_post_create_state.dart';
@@ -620,16 +621,16 @@ class _BlogPostCreateScreenState extends State {
}
},
borderRadius: BorderRadius.circular(16),
- child: Ink(
- width: MediaQuery.of(context).size.width - 32,
- height: 52,
- decoration: BoxDecoration(borderRadius: BorderRadius.circular(16), color: appTheme.blueA400),
- child: isLoading
- ? const Center(child: CircularProgressIndicator())
- : const Center(
+ child: isLoading
+ ? const Center(child: Loader())
+ : Ink(
+ width: MediaQuery.of(context).size.width - 32,
+ height: 52,
+ decoration: BoxDecoration(borderRadius: BorderRadius.circular(16), color: appTheme.blueA400),
+ child: const Center(
child: Text('Создать', style: TextStyle(color: Colors.white)),
),
- ),
+ ),
);
}
diff --git a/lib/features/create_recipe/presentation/pages/recipe_create_screen.dart b/lib/features/create_recipe/presentation/pages/recipe_create_screen.dart
index c8a6d4ef..932852ac 100644
--- a/lib/features/create_recipe/presentation/pages/recipe_create_screen.dart
+++ b/lib/features/create_recipe/presentation/pages/recipe_create_screen.dart
@@ -33,7 +33,7 @@ class _RecipeCreateScreenState extends State {
late final CreateRecipeCubit createRecipeCubit;
final GlobalKey _mdKey = GlobalKey();
bool _mdToolbarVisible = false;
-
+ bool _isRecipeBeingPublished = false;
@override
void initState() {
super.initState();
@@ -58,9 +58,7 @@ class _RecipeCreateScreenState extends State {
return Scaffold(
backgroundColor: const Color(0xFFF0F2F5),
floatingActionButtonLocation: FloatingActionButtonLocation.centerFloat,
- floatingActionButton: _mdToolbarVisible && _mdKey.currentState != null
- ? _mdKey.currentState!.buildToolbar(context)
- : null,
+ floatingActionButton: _mdToolbarVisible && _mdKey.currentState != null ? _mdKey.currentState!.buildToolbar(context) : null,
appBar: AppBar(
title: widget.recipe != null ? const Text('Редактирование рецепта') : const Text('Добавление рецепта'),
centerTitle: true,
@@ -83,44 +81,55 @@ class _RecipeCreateScreenState extends State {
builder: (context, state) {
return Builder(
builder: (context) {
- return InkWell(
- key: AppKeys.recipeCreateScreenPublishButton,
- onTap: () async {
- if (context.read().validateAll()) {
- final isSuccess = await context.read().publishRecipe(
- widget.recipe == null ? false : true,
- widget.recipe?.slug,
+ return _isRecipeBeingPublished
+ ? const Center(child: Loader())
+ : InkWell(
+ key: AppKeys.recipeCreateScreenPublishButton,
+ onTap: () async {
+ setState(() {
+ _isRecipeBeingPublished = true;
+ });
+ if (context.read().validateAll()) {
+ final isSuccess = await context.read().publishRecipe(
+ widget.recipe == null ? false : true,
+ widget.recipe?.slug,
+ );
+ setState(() {
+ _isRecipeBeingPublished = false;
+ });
+ if (isSuccess) {
+ Future.delayed(const Duration(milliseconds: 500), () {
+ if (!context.mounted) return;
+ context.pop();
+ showBottomSheetWithBlur(
+ userRootNavigator: true,
+ context: context,
+ child: SuccessPublishedRecipeBottomSheet(title: state.entity.title ?? 'Рецепт'),
+ );
+ });
+ } else {
+ if (!context.mounted) return;
+ showErrorDialog(context, title: '${s.could_not_publish_recipe} ${state.entity.title}');
+ }
+ }
+ setState(() {
+ _isRecipeBeingPublished = false;
+ });
+ },
+ borderRadius: BorderRadius.circular(20),
+ child: Ink(
+ width: MediaQuery.of(context).size.width - 32,
+ decoration: BoxDecoration(color: appTheme.blueA400, borderRadius: BorderRadius.circular(20)),
+ child: Container(
+ padding: const EdgeInsets.all(16),
+ alignment: Alignment.center,
+ child: Text(
+ widget.recipe != null ? 'Обновить' : 'Опубликовать',
+ style: const TextStyle(fontSize: 16, color: Colors.white, fontWeight: FontWeight.w600),
+ ),
+ ),
+ ),
);
- if (isSuccess) {
- Future.delayed(const Duration(milliseconds: 500), () {
- if (!context.mounted) return;
- context.pop();
- showBottomSheetWithBlur(
- userRootNavigator: true,
- context: context,
- child: SuccessPublishedRecipeBottomSheet(title: state.entity.title ?? 'Рецепт'),
- );
- });
- } else {
- if (!context.mounted) return;
- showErrorDialog(context, title: '${s.could_not_publish_recipe} ${state.entity.title}');
- }
- }
- },
- borderRadius: BorderRadius.circular(20),
- child: Ink(
- width: MediaQuery.of(context).size.width - 32,
- decoration: BoxDecoration(color: appTheme.blueA400, borderRadius: BorderRadius.circular(20)),
- child: Container(
- padding: const EdgeInsets.all(16),
- alignment: Alignment.center,
- child: Text(
- widget.recipe != null ? 'Обновить' : 'Опубликовать',
- style: const TextStyle(fontSize: 16, color: Colors.white, fontWeight: FontWeight.w600),
- ),
- ),
- ),
- );
},
);
},
diff --git a/lib/features/diary/presentation/pages/diary_screen.dart b/lib/features/diary/presentation/pages/diary_screen.dart
index ae6f9e10..607e97d2 100644
--- a/lib/features/diary/presentation/pages/diary_screen.dart
+++ b/lib/features/diary/presentation/pages/diary_screen.dart
@@ -44,16 +44,24 @@ class UserDiaryScreen extends StatelessWidget {
}
}
-class DiaryScreenContent extends StatelessWidget {
+class DiaryScreenContent extends StatefulWidget {
const DiaryScreenContent({super.key, this.diaryId, this.currentPlanDay});
final int? diaryId;
final int? currentPlanDay;
+
+ @override
+ State createState() => _DiaryScreenContentState();
+}
+
+class _DiaryScreenContentState extends State {
+ bool _isDiaryFinishing = false;
+
@override
Widget build(BuildContext context) {
final s = S.of(context);
return MultiBlocProvider(
providers: [
- BlocProvider(create: (BuildContext context) => sl()..fetchDiaryV2(needLoading: true, diaryId: diaryId)),
+ BlocProvider(create: (BuildContext context) => sl()..fetchDiaryV2(needLoading: true, diaryId: widget.diaryId)),
BlocProvider(create: (context) => sl()),
],
child: BlocBuilder(
@@ -63,13 +71,13 @@ class DiaryScreenContent extends StatelessWidget {
}
if (state is DiaryStateError) {
return Scaffold(
- body: DataErrorWidget(onReload: () => context.read().fetchDiaryV2(needLoading: false, diaryId: diaryId)),
+ body: DataErrorWidget(onReload: () => context.read().fetchDiaryV2(needLoading: false, diaryId: widget.diaryId)),
);
}
return Scaffold(
backgroundColor: Colors.grey.shade200,
- appBar: diaryId == null
+ appBar: widget.diaryId == null
? AppBar(toolbarHeight: 0)
: AppBar(
title: Text(
@@ -83,7 +91,7 @@ class DiaryScreenContent extends StatelessWidget {
physics: const BouncingScrollPhysics(),
child: Column(
children: [
- if (diaryId == null) const CalendarWidget(),
+ if (widget.diaryId == null) const CalendarWidget(),
if (state is DiaryStateLoading) ...[const SizedBox(height: 60), const Loader()],
if (state is DiaryStateLoaded)
WakeupAndRetireWidget(
@@ -116,7 +124,23 @@ class DiaryScreenContent extends StatelessWidget {
if (state is DiaryStateLoaded && !state.isConsultantView && !state.isDiaryFinished && !state.isDiaryCommented)
Padding(
padding: const EdgeInsets.only(top: 12),
- child: FinishDayButtonWidget(diaryDay: state.diary),
+ child: _isDiaryFinishing
+ ? const Center(child: Loader())
+ : FinishDayButtonWidget(
+ onPressed: () async {
+ setState(() {
+ _isDiaryFinishing = true;
+ });
+ await context.read().finishDiaryDay(
+ diaryId: state.diary.id,
+ wakeUp: state.diary.wakeup.toString(),
+ retire: state.diary.retire.toString(),
+ );
+ setState(() {
+ _isDiaryFinishing = false;
+ });
+ },
+ ),
),
if (state is DiaryStateLoaded && state.isConsultantView && !state.isDiaryCommented) ...[
const SizedBox(height: 16),
diff --git a/lib/features/diary/presentation/widgets/finish_day_button_widget.dart b/lib/features/diary/presentation/widgets/finish_day_button_widget.dart
index b3f170ec..af58216a 100644
--- a/lib/features/diary/presentation/widgets/finish_day_button_widget.dart
+++ b/lib/features/diary/presentation/widgets/finish_day_button_widget.dart
@@ -1,13 +1,10 @@
-import 'package:eatvio_app/features/diary/entities/diary_day_entity.dart';
-import 'package:eatvio_app/features/diary/presentation/bloc/diary_cubit.dart';
import 'package:eatvio_app/core/theme/theme_helper.dart';
import 'package:flutter/material.dart';
-import 'package:flutter_bloc/flutter_bloc.dart';
class FinishDayButtonWidget extends StatefulWidget {
- final DiaryDayEntity diaryDay;
+ final Future Function() onPressed;
- const FinishDayButtonWidget({super.key, required this.diaryDay});
+ const FinishDayButtonWidget({super.key, required this.onPressed});
@override
State createState() => _FinishDayButtonWidgetState();
@@ -17,11 +14,7 @@ class _FinishDayButtonWidgetState extends State {
@override
Widget build(BuildContext context) {
return InkWell(
- onTap: () => context.read().finishDiaryDay(
- diaryId: widget.diaryDay.id,
- wakeUp: widget.diaryDay.wakeup.toString(),
- retire: widget.diaryDay.retire.toString(),
- ),
+ onTap: widget.onPressed,
borderRadius: BorderRadius.circular(20),
child: Ink(
padding: const EdgeInsets.symmetric(horizontal: 8),
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..0a55ecff
--- /dev/null
+++ b/lib/features/registration_v2/cubit/registration_cubit.dart
@@ -0,0 +1,239 @@
+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:flutter_password_generator/flutter_password_generator.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 = FlutterPasswordGenerator(length: 8);
+ final result = await generator.generatePassword();
+ final password = result['password'] as String;
+ 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
new file mode 100644
index 00000000..de26c146
--- /dev/null
+++ b/lib/features/registration_v2/cubit/registration_state.dart
@@ -0,0 +1,32 @@
+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;
+}
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..0fe9239f
--- /dev/null
+++ b/lib/features/registration_v2/models/registration_model.dart
@@ -0,0 +1,24 @@
+import 'package:freezed_annotation/freezed_annotation.dart';
+
+part 'registration_model.freezed.dart';
+part 'registration_model.g.dart';
+
+@freezed
+abstract class RegistrationModel with _$RegistrationModel {
+
+ factory RegistrationModel({
+ @JsonKey(name: 'first_name') required String firstName,
+ @JsonKey(name: 'last_name') required String lastName,
+ @JsonKey(name: 'email') required String email,
+ @JsonKey(name: 'phone') required String phone,
+ @JsonKey(name: 'gender') required String gender,
+ @JsonKey(name: 'password') required String password,
+ @JsonKey(name: 'password_confirmation') required String confirmPassword,
+ @JsonKey(name: 'weight') required int weight,
+ @JsonKey(name: 'height') required int height,
+ @JsonKey(name: 'activity_coefficient') required double activityCoefficient,
+ @JsonKey(name: 'birthdate') required String birthday,
+ }) = _RegistrationModel;
+
+ factory RegistrationModel.fromJson(Map json) => _$RegistrationModelFromJson(json);
+}
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..d69d27c4
--- /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) const 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..8f4b5372
--- /dev/null
+++ b/lib/features/registration_v2/presentation/widgets/first_step_content.dart
@@ -0,0 +1,176 @@
+import 'package:eatvio_app/core/theme/custom_text_style.dart';
+import 'package:eatvio_app/core/theme/theme_helper.dart';
+import 'package:eatvio_app/core/utils/date_time_utils.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/create_diet/presentation/widgets/error_widget.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/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 StatefulWidget {
+ const FirstStepContent({super.key});
+
+ @override
+ State createState() => _FirstStepContentState();
+}
+
+class _FirstStepContentState extends State {
+ final maskFormatterForBirthday = MaskTextInputFormatter(mask: '##.##.####', filter: {"#": RegExp(r'[0-9]')}, type: MaskAutoCompletionType.lazy);
+
+ final firstNameController = TextEditingController();
+
+ final lastNameController = TextEditingController();
+
+ final emailController = TextEditingController();
+
+ final phoneController = TextEditingController();
+
+ final dateController = TextEditingController();
+ @override
+ void initState() {
+ super.initState();
+ firstNameController.text = context.read().state.firstName;
+ lastNameController.text = context.read().state.lastName;
+ emailController.text = context.read().state.email;
+ phoneController.text = context.read().state.phone;
+ dateController.text = context.read().state.birthday?.format('dd.MM.yyyy') ?? '';
+ }
+
+ @override
+ Widget build(BuildContext context) {
+ final s = S.of(context);
+ return BlocBuilder(
+ builder: (context, state) {
+ 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,
+ hasError: !state.isFirstNameValid,
+ controller: firstNameController,
+ onChanged: (val) {
+ context.read().setFirstName(val);
+ },
+ ),
+ if (!state.isFirstNameValid) ...[const SizedBox(height: 4), ValidationErrorWidget(error: s.first_name_validation_error)],
+ ],
+ ),
+ ),
+ 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,
+ hasError: !state.isLastNameValid,
+ controller: lastNameController,
+ onChanged: (val) {
+ context.read().setLastName(val);
+ },
+ ),
+ if (!state.isLastNameValid) ...[const SizedBox(height: 4), ValidationErrorWidget(error: s.last_name_error)],
+ ],
+ ),
+ ),
+ ],
+ ),
+ const SizedBox(height: 12),
+ Text(s.email, style: TextStyles.textSMedium.copyWith(color: appTheme.black)),
+ CustomTextFormField(
+ hintText: s.type_email,
+ hasError: !state.isEmailValid,
+ controller: emailController,
+ onChanged: (val) {
+ context.read().setEmail(val);
+ },
+ ),
+ if (!state.isEmailValid) ...[
+ const SizedBox(height: 4),
+ ValidationErrorWidget(error: state.emailDisplayError.isEmpty ? s.email_error : state.emailDisplayError),
+ ],
+ const SizedBox(height: 12),
+ Text(s.phone, style: TextStyles.textSMedium.copyWith(color: appTheme.black)),
+ Column(
+ children: [
+ CustomTextFormField(
+ hintText: '+7 (999) 999 99 99',
+ controller: phoneController,
+ hasError: !state.isPhoneValid,
+ onChanged: (val) {
+ context.read().setPhone(val);
+ },
+ ),
+ if (!state.isPhoneValid) ...[
+ const SizedBox(height: 4),
+ ValidationErrorWidget(error: state.phoneDisplayError.isEmpty ? s.phone_error : state.phoneDisplayError),
+ ],
+ ],
+ ),
+ const SizedBox(height: 12),
+ Row(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ 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],
+ hasError: !state.isBirthdayValid,
+ controller: dateController,
+ onChanged: (val) {
+ final dateArray = val.split('.');
+ if (dateArray.length != 3) return;
+ final date = DateTime(int.parse(dateArray[2]), int.parse(dateArray[1]), int.parse(dateArray[0]));
+ context.read().setBirthday(date);
+ },
+ ),
+ if (!state.isBirthdayValid) ...[
+ const SizedBox(height: 4),
+ ValidationErrorWidget(error: state.dateOfBirthDisplayError.isEmpty ? s.date_of_birth_error : state.dateOfBirthDisplayError),
+ ],
+ ],
+ ),
+ ),
+ 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..859f9b67
--- /dev/null
+++ b/lib/features/registration_v2/presentation/widgets/password_field.dart
@@ -0,0 +1,76 @@
+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/services.dart';
+import 'package:flutter_bloc/flutter_bloc.dart';
+import 'package:flutter_svg/svg.dart';
+
+class PasswordField extends StatefulWidget {
+ const PasswordField({super.key, required this.cubit});
+ final RegistrationCubitV2 cubit;
+ @override
+ State createState() => _PasswordFieldState();
+}
+
+class _PasswordFieldState extends State {
+ bool copied = false;
+ @override
+ Widget build(BuildContext context) {
+ final s = S.of(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(state.password, style: TextStyles.textMMediumAuto.copyWith(color: appTheme.black)),
+ const Spacer(),
+ GestureDetector(
+ onTap: copied
+ ? null
+ : () async {
+ Clipboard.setData(ClipboardData(text: state.password));
+ setState(() {
+ copied = true;
+ });
+ Future.delayed(const Duration(seconds: 3), () {
+ setState(() {
+ copied = false;
+ });
+ });
+ },
+ child: SvgPicture.asset(
+ copied ? 'assets/icons/check_filled.svg' : 'assets/icons/copy2.svg',
+ height: 20,
+ width: 20,
+ colorFilter: copied ? null : ColorFilter.mode(appTheme.black, BlendMode.srcIn),
+ ),
+ ),
+ ],
+ ),
+ ),
+ if (copied) ...[
+ const SizedBox(height: 6),
+ Row(
+ children: [
+ SvgPicture.asset('assets/icons/check.svg', height: 10, width: 10),
+ const SizedBox(width: 4),
+ Text(s.copied2, style: TextStyles.textSMedium.copyWith(color: appTheme.darkGreen)),
+ ],
+ ),
+ ],
+ SizedBox(height: copied ? 18 : 12),
+ ],
+ );
+ },
+ );
+ }
+}
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..86bb1fb8
--- /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 = [];
+ final List activityDropDownValues = [1.2, 1.375, 1.55, 1.7, 1.9];
+ @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(
+ isExpanded: true,
+ onChanged: (value) {
+ if (value != null) {
+ context.read().setActivityCoefficient(value);
+ }
+ },
+ items: activityDropDownTitles
+ .mapIndexed(
+ (index, value) => DropdownMenuItem(
+ value: activityDropDownValues[index],
+ child: Text(activityDropDownTitles[index], style: TextStyles.textMMedium.copyWith(color: appTheme.black)),
+ ),
+ )
+ .toList(),
+ value: state.activityCoefficient == 0 ? null : state.activityCoefficient,
+ selectedItemBuilder: (context) {
+ return activityDropDownTitles.mapIndexed((index, value) => Text(activityDropDownTitles[index], overflow: TextOverflow.clip, maxLines: 1)).toList();
+ },
+ ),
+ ],
+ );
+ },
+ );
+ }
+}
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..45efc931
--- /dev/null
+++ b/lib/features/registration_v2/presentation/widgets/second_step_content.dart
@@ -0,0 +1,175 @@
+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/loader.dart';
+import 'package:eatvio_app/features/create_diet/presentation/widgets/error_widget.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/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 StatefulWidget {
+ const SecondStepContent({super.key});
+
+ @override
+ State createState() => _SecondStepContentState();
+}
+
+class _SecondStepContentState extends State {
+ bool isLoading = false;
+ final weightController = TextEditingController();
+ final heightController = TextEditingController();
+
+ @override
+ void initState() {
+ super.initState();
+ heightController.text = context.read().state.height.toString();
+ weightController.text = context.read().state.weight.toString();
+ }
+
+ @override
+ Widget build(BuildContext context) {
+ final s = S.of(context);
+ return BlocBuilder(
+ builder: (context, state) {
+ 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,
+ hasError: !state.isHeightValid,
+ controller: heightController,
+ textInputType: const TextInputType.numberWithOptions(),
+ onChanged: (val) {
+ context.read().setHeight(int.parse(val));
+ },
+ ),
+ if (!state.isHeightValid) ValidationErrorWidget(error: s.height_error),
+ ],
+ ),
+ ),
+ 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,
+ hasError: !state.isWeightValid,
+ controller: weightController,
+ textInputType: const TextInputType.numberWithOptions(),
+ onChanged: (val) {
+ context.read().setWeight(int.parse(val));
+ },
+ ),
+ if (!state.isWeightValid) ValidationErrorWidget(error: s.weight_error),
+ ],
+ ),
+ ),
+ ],
+ ),
+ const SizedBox(height: 12),
+ Text(s.phisical_activity, 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),
+ isLoading
+ ? const Expanded(child: Center(child: Loader()))
+ : Expanded(
+ child: CustomOutlinedButton(
+ onPressed: () async {
+ setState(() {
+ isLoading = true;
+ });
+ final cubit = context.read();
+ final (commonResult, hasValidationError) = await cubit.register();
+ setState(() {
+ isLoading = false;
+ });
+ if (context.mounted && !hasValidationError) {
+ showRegistrationBottomSheet(context, cubit, commonResult);
+ }
+ },
+ 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..60f0944f
--- /dev/null
+++ b/lib/features/registration_v2/presentation/widgets/sex_widget.dart
@@ -0,0 +1,98 @@
+import 'package:eatvio_app/core/theme/custom_text_style.dart';
+import 'package:eatvio_app/core/theme/theme_helper.dart';
+import 'package:eatvio_app/features/create_diet/presentation/widgets/error_widget.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 Column(
+ children: [
+ 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: 60,
+ decoration: BoxDecoration(
+ color: const Color(0xFFF6F6F6),
+ borderRadius: BorderRadius.circular(20),
+ border: state.isGenderValid
+ ? state.gender == 'male'
+ ? Border.all(width: 2, color: appTheme.blueA400)
+ : null
+ : Border.all(width: 1, color: appTheme.red),
+ ),
+ 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: 60,
+ decoration: BoxDecoration(
+ color: const Color(0xFFF6F6F6),
+ borderRadius: BorderRadius.circular(20),
+ border: state.isGenderValid
+ ? state.gender == 'female'
+ ? Border.all(width: 2, color: appTheme.blueA400)
+ : null
+ : Border.all(width: 1, color: appTheme.red),
+ ),
+ child: Center(
+ child: Text(
+ s.female_short,
+ style: TextStyle(
+ color: state.gender == 'female' ? appTheme.blueA400 : const Color(0xFF323F4B),
+ fontSize: 16,
+ fontWeight: FontWeight.w500,
+ ),
+ ),
+ ),
+ ),
+ ),
+ ),
+ ],
+ ),
+ if (!state.isGenderValid) ...[const SizedBox(height: 4), ValidationErrorWidget(error: s.gender_error)],
+ ],
+ );
+ },
+ );
+ }
+}
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..7be50ec2
--- /dev/null
+++ b/lib/features/registration_v2/presentation/widgets/show_successful_registration_bottom_sheet.dart
@@ -0,0 +1,106 @@
+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),
+ ],
+ ),
+ );
+ }
+}
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..7bb3497c
--- /dev/null
+++ b/lib/features/registration_v2/presentation/widgets/step_progress.dart
@@ -0,0 +1,24 @@
+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 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/features/registration_v2/repositories/registration_repository_v2.dart b/lib/features/registration_v2/repositories/registration_repository_v2.dart
new file mode 100644
index 00000000..b30e61d8
--- /dev/null
+++ b/lib/features/registration_v2/repositories/registration_repository_v2.dart
@@ -0,0 +1,16 @@
+import 'package:eatvio_app/features/registration_v2/models/registration_model.dart';
+import 'package:eatvio_app/features/registration_v2/services/registration_service_v2.dart';
+
+class RegistrationRepositoryV2 {
+ final RegistrationServiceV2 registrationService;
+ RegistrationRepositoryV2({required this.registrationService});
+
+ Future register({required RegistrationModel registrationModel}) async {
+ try {
+ final response = await registrationService.register(model: registrationModel.toJson());
+ return response['token'];
+ } catch (e) {
+ rethrow;
+ }
+ }
+}
diff --git a/lib/features/registration_v2/services/registration_service_v2.dart b/lib/features/registration_v2/services/registration_service_v2.dart
new file mode 100644
index 00000000..2472eb40
--- /dev/null
+++ b/lib/features/registration_v2/services/registration_service_v2.dart
@@ -0,0 +1,15 @@
+import 'package:dio/dio.dart';
+import 'package:retrofit/retrofit.dart';
+
+part 'registration_service_v2.g.dart';
+
+@RestApi()
+abstract class RegistrationServiceV2 {
+ factory RegistrationServiceV2(Dio dio, {String baseUrl}) = _RegistrationServiceV2;
+
+ @POST('/api/register')
+ Future