chore: update version to 1.3.11+91 in pubspec.yaml; clean up whitespace in various files

This commit is contained in:
mso7nueh
2025-12-26 13:51:44 +03:00
33 changed files with 900 additions and 819 deletions

View File

@@ -31,3 +31,6 @@ abstract class OAuthVerificationConfirmResponse with _$OAuthVerificationConfirmR

View File

@@ -25,3 +25,6 @@ abstract class OAuthVerificationRequestResponse with _$OAuthVerificationRequestR

View File

@@ -1,166 +1,124 @@
import 'package:eatvio_app/core/locator_service.dart';
import 'package:eatvio_app/core/theme/theme_helper.dart';
import 'package:eatvio_app/core/utils/toasts.dart';
import 'package:eatvio_app/core/widgets/custom_text_form_field.dart';
import 'package:eatvio_app/features/auth/presentation/bloc/reset_password_cubit/reset_password_cubit.dart';
import 'package:eatvio_app/features/auth/presentation/bloc/reset_password_cubit/reset_password_state.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:go_router/go_router.dart';
class ForgetPasswordScreen extends StatefulWidget {
final bool isAuth;
const ForgetPasswordScreen({super.key, required this.isAuth});
@override
State<ForgetPasswordScreen> createState() => _ForgetPasswordScreenState();
}
class _ForgetPasswordScreenState extends State<ForgetPasswordScreen> {
final emailController = TextEditingController();
final codeController = TextEditingController();
final passwordController = TextEditingController();
final passwordConfirmController = TextEditingController();
@override
Widget build(BuildContext context) {
return BlocProvider<ResetPasswordCubit>(
create: (BuildContext context) => sl<ResetPasswordCubit>(),
child: BlocConsumer<ResetPasswordCubit, ResetPasswordState>(
builder: (BuildContext context, ResetPasswordState state) {
return Scaffold(
appBar: AppBar(
title: widget.isAuth ? const Text('Смена пароля') : const Text('Восстановление пароля'),
centerTitle: false,
),
body: state is ResetPasswordStateLoadingResetPassword || state is ResetPasswordStateLoadingSendEmailCode
? const Center(
child: CircularProgressIndicator(),
)
: Padding(
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
if (state is ResetPasswordStateCodeSendError || state is ResetPasswordStateEmpty) ...[
Text(
'${widget.isAuth ? '' : 'Забыли пароль? '}Укажите свой email,\nи мы пришлём код для сброса.',
style: const TextStyle(
fontSize: 16,
fontWeight: FontWeight.w500,
),
),
const SizedBox(height: 24),
const Text(
'E-mail',
style: TextStyle(
fontSize: 14,
fontWeight: FontWeight.w500,
),
),
const SizedBox(height: 4),
CustomTextFormField(
controller: emailController,
hintText: 'Введите e-mail',
),
],
if (state is ResetPasswordStateCodeSendSuccess || state is ResetPasswordStateResetPasswordError) ...[
const Text(
'Код',
style: TextStyle(
fontSize: 14,
fontWeight: FontWeight.w500,
),
),
const SizedBox(height: 4),
CustomTextFormField(
controller: codeController,
hintText: 'Введите код',
),
const SizedBox(height: 12),
const Text(
'Пароль',
style: TextStyle(
fontSize: 14,
fontWeight: FontWeight.w500,
),
),
const SizedBox(height: 4),
CustomTextFormField(
controller: passwordController,
hintText: 'Введите новый пароль',
),
const SizedBox(height: 12),
const Text(
'Подтвердите пароль',
style: TextStyle(
fontSize: 14,
fontWeight: FontWeight.w500,
),
),
const SizedBox(height: 4),
CustomTextFormField(
controller: passwordConfirmController,
hintText: 'Введите новый пароль',
),
],
const Spacer(),
InkWell(
onTap: () {
if (state is ResetPasswordStateCodeSendSuccess || state is ResetPasswordStateResetPasswordError) {
context.read<ResetPasswordCubit>().tryResetPassword(
emailController.text,
codeController.text,
passwordController.text,
passwordConfirmController.text,
);
} else {
context.read<ResetPasswordCubit>().trySendEmailCode(emailController.text);
}
},
borderRadius: BorderRadius.circular(20),
child: Ink(
width: MediaQuery.of(context).size.width - 32,
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(20),
color: appTheme.blueA400,
),
child: Center(
child: Text(
state is ResetPasswordStateCodeSendSuccess || state is ResetPasswordStateResetPasswordError
? 'Сменить пароль'
: 'Сбросить пароль',
style: const TextStyle(
fontSize: 18,
fontWeight: FontWeight.w600,
color: Colors.white,
),
),
),
),
),
],
),
),
);
},
listener: (BuildContext context, ResetPasswordState state) {
if (state is ResetPasswordStateCodeSendSuccess) {
showSuccessToast('Код на почту успешно отправлен');
}
if (state is ResetPasswordStateCodeSendError) {
showErrorToast('Ошибка отправки кода. Проверьте правильность ввёдной почты');
}
if (state is ResetPasswordStateResetPasswordError) {
showErrorToast('Ошибка смены пароля. Проверьте правильность ввёднного кода и совпадение пароля');
}
if (state is ResetPasswordStateResetPassowrdSuccess) {
GoRouter.of(context).pop();
showSuccessToast('Пароль успешно сменён. ${widget.isAuth ? '' : 'Войдите в аккаунт'}');
}
},
),
);
}
}
import 'package:eatvio_app/core/locator_service.dart';
import 'package:eatvio_app/core/theme/theme_helper.dart';
import 'package:eatvio_app/core/utils/toasts.dart';
import 'package:eatvio_app/core/widgets/custom_text_form_field.dart';
import 'package:eatvio_app/features/auth/presentation/bloc/reset_password_cubit/reset_password_cubit.dart';
import 'package:eatvio_app/features/auth/presentation/bloc/reset_password_cubit/reset_password_state.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:go_router/go_router.dart';
class ForgetPasswordScreen extends StatefulWidget {
final bool isAuth;
const ForgetPasswordScreen({super.key, required this.isAuth});
@override
State<ForgetPasswordScreen> createState() => _ForgetPasswordScreenState();
}
class _ForgetPasswordScreenState extends State<ForgetPasswordScreen> {
final emailController = TextEditingController();
final codeController = TextEditingController();
final passwordController = TextEditingController();
final passwordConfirmController = TextEditingController();
@override
Widget build(BuildContext context) {
return BlocProvider<ResetPasswordCubit>(
create: (BuildContext context) => sl<ResetPasswordCubit>(),
child: BlocConsumer<ResetPasswordCubit, ResetPasswordState>(
builder: (BuildContext context, ResetPasswordState state) {
return Scaffold(
appBar: AppBar(title: widget.isAuth ? const Text('Смена пароля') : const Text('Восстановление пароля'), centerTitle: false),
body: state is ResetPasswordStateLoadingResetPassword || state is ResetPasswordStateLoadingSendEmailCode
? const Center(child: CircularProgressIndicator())
: Padding(
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
if (state is ResetPasswordStateCodeSendError || state is ResetPasswordStateEmpty) ...[
Text(
'${widget.isAuth ? '' : 'Забыли пароль? '}Укажите свой email,\nи мы пришлём код для сброса.',
style: const TextStyle(fontSize: 16, fontWeight: FontWeight.w500),
),
const SizedBox(height: 24),
const Text('E-mail', style: TextStyle(fontSize: 14, fontWeight: FontWeight.w500)),
const SizedBox(height: 4),
CustomTextFormField(controller: emailController, hintText: 'Введите e-mail'),
],
if (state is ResetPasswordStateCodeSendSuccess || state is ResetPasswordStateResetPasswordError) ...[
const Text('Код', style: TextStyle(fontSize: 14, fontWeight: FontWeight.w500)),
const SizedBox(height: 4),
CustomTextFormField(controller: codeController, hintText: 'Введите код'),
const SizedBox(height: 12),
const Text('Пароль', style: TextStyle(fontSize: 14, fontWeight: FontWeight.w500)),
const SizedBox(height: 4),
CustomTextFormField(
controller: passwordController,
hintText: 'Введите новый пароль',
inputFormatters: [FilteringTextInputFormatter.deny(RegExp(r'\s'))],
),
const SizedBox(height: 12),
const Text('Подтвердите пароль', style: TextStyle(fontSize: 14, fontWeight: FontWeight.w500)),
const SizedBox(height: 4),
CustomTextFormField(
controller: passwordConfirmController,
hintText: 'Введите новый пароль',
inputFormatters: [FilteringTextInputFormatter.deny(RegExp(r'\s'))],
),
],
const Spacer(),
InkWell(
onTap: () {
if (state is ResetPasswordStateCodeSendSuccess || state is ResetPasswordStateResetPasswordError) {
context.read<ResetPasswordCubit>().tryResetPassword(
emailController.text,
codeController.text,
passwordController.text,
passwordConfirmController.text,
);
} else {
context.read<ResetPasswordCubit>().trySendEmailCode(emailController.text);
}
},
borderRadius: BorderRadius.circular(20),
child: Ink(
width: MediaQuery.of(context).size.width - 32,
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(borderRadius: BorderRadius.circular(20), color: appTheme.blueA400),
child: Center(
child: Text(
state is ResetPasswordStateCodeSendSuccess || state is ResetPasswordStateResetPasswordError
? 'Сменить пароль'
: 'Сбросить пароль',
style: const TextStyle(fontSize: 18, fontWeight: FontWeight.w600, color: Colors.white),
),
),
),
),
],
),
),
);
},
listener: (BuildContext context, ResetPasswordState state) {
if (state is ResetPasswordStateCodeSendSuccess) {
showSuccessToast('Код на почту успешно отправлен');
}
if (state is ResetPasswordStateCodeSendError) {
showErrorToast('Ошибка отправки кода. Проверьте правильность ввёдной почты');
}
if (state is ResetPasswordStateResetPasswordError) {
showErrorToast('Ошибка смены пароля. Проверьте правильность ввёднного кода и совпадение пароля');
}
if (state is ResetPasswordStateResetPassowrdSuccess) {
GoRouter.of(context).pop();
showSuccessToast('Пароль успешно сменён. ${widget.isAuth ? '' : 'Войдите в аккаунт'}');
}
},
),
);
}
}

View File

@@ -19,3 +19,6 @@ abstract class BarcodeService {

View File

@@ -1,378 +1,388 @@
import 'package:eatvio_app/core/utils/image_constant.dart';
import 'package:eatvio_app/core/widgets/loader.dart';
import 'package:eatvio_app/features/registration/presentation/bloc/registration_cubit/registration_cubit.dart';
import 'package:eatvio_app/features/registration/presentation/bloc/registration_cubit/registration_state.dart';
import 'package:eatvio_app/features/registration/presentation/pages/additional_information_screen.dart';
import 'package:eatvio_app/features/registration/presentation/pages/allergies_screen.dart';
import 'package:eatvio_app/features/registration/presentation/pages/blood_pressure_screen.dart';
import 'package:eatvio_app/features/registration/presentation/pages/body_circumferences_screen.dart';
import 'package:eatvio_app/features/registration/presentation/pages/city_of_living_screen.dart';
import 'package:eatvio_app/features/registration/presentation/pages/credentials_screen.dart';
import 'package:eatvio_app/features/registration/presentation/pages/date_of_birth_screen.dart';
import 'package:eatvio_app/features/registration/presentation/pages/favorite_foods_screen.dart';
import 'package:eatvio_app/features/registration/presentation/pages/favorite_sports_screen.dart';
import 'package:eatvio_app/features/registration/presentation/pages/gender_screen.dart';
import 'package:eatvio_app/features/registration/presentation/pages/height_screen.dart';
import 'package:eatvio_app/features/registration/presentation/pages/medicine_intake_screen.dart';
import 'package:eatvio_app/features/registration/presentation/pages/name_screen.dart';
import 'package:eatvio_app/features/registration/presentation/pages/phone_screen.dart';
import 'package:eatvio_app/features/registration/presentation/pages/weight_screen.dart';
import 'package:eatvio_app/features/registration/services/registration_analytics_service.dart';
import 'package:eatvio_app/generated/l10n.dart';
import 'package:eatvio_app/core/locator_service.dart';
import 'package:eatvio_app/core/theme/theme_helper.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_svg/flutter_svg.dart';
import 'package:go_router/go_router.dart';
import 'package:validators/validators.dart';
class RegistrationScreen extends StatefulWidget {
const RegistrationScreen({super.key});
@override
State<RegistrationScreen> createState() => _RegistrationScreenState();
}
class _RegistrationScreenState extends State<RegistrationScreen> {
double progressValue = 1 / 15;
int step = 1;
final pageController = PageController();
final RegistrationCubit registrationCubit = sl<RegistrationCubit>();
@override
void initState() {
super.initState();
RegistrationAnalyticsService.reportRegistrationStep(step);
}
@override
Widget build(BuildContext context) {
return BlocProvider<RegistrationCubit>(
create: (BuildContext context) => registrationCubit,
child: BlocListener<RegistrationCubit, RegistrationState>(
listener: (context, state) {
if (state.error != null && state.error!.isNotEmpty) {
showDialog(
context: context,
builder: (context) => AlertDialog(
title: const Text('Ошибка при регистрации'),
content: Text(state.error!),
actions: [TextButton(onPressed: () => Navigator.pop(context), child: const Text('OK'))],
),
);
}
},
child: BlocBuilder<RegistrationCubit, RegistrationState>(
builder: (BuildContext context, RegistrationState state) {
return Scaffold(
floatingActionButtonLocation: FloatingActionButtonLocation.centerFloat,
floatingActionButton: Row(
children: [
Container(
height: 48,
width: MediaQuery.of(context).size.width * 0.2,
padding: const EdgeInsets.only(left: 16, right: 4),
child: FloatingActionButton.extended(
heroTag: 'backButtonRegistration',
highlightElevation: 0,
focusElevation: 0,
elevation: 0,
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(20)),
backgroundColor: const Color(0xFFEAF2FD),
onPressed: () async {
setState(() {
pageController.previousPage(duration: const Duration(milliseconds: 350), curve: Curves.easeInOut);
});
},
label: Icon(Icons.arrow_back_ios_new_rounded, color: appTheme.blueA400),
),
),
Container(
height: 48,
width: MediaQuery.of(context).size.width * 0.8,
padding: const EdgeInsets.only(left: 4, right: 16),
child: FloatingActionButton.extended(
heroTag: 'nextButtonRegistration',
highlightElevation: 0,
elevation: 0,
focusElevation: 0,
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(20)),
backgroundColor: appTheme.blueA400,
onPressed: () async {
if (await canGoNext(state, context)) {
setState(() {
pageController.nextPage(duration: const Duration(milliseconds: 350), curve: Curves.easeInOut);
});
}
},
label: Text('Продолжить', style: theme.textTheme.titleSmall!.copyWith(color: Colors.white)),
),
),
],
),
appBar: AppBar(
automaticallyImplyLeading: false,
leading: Builder(
builder: (context) => IconButton(icon: const Icon(Icons.close_rounded), onPressed: () => GoRouter.of(context).pop()),
),
centerTitle: true,
title: SvgPicture.asset(ImageConstant.imgLogoEatvio, height: 48, fit: BoxFit.contain),
actions: [Padding(padding: const EdgeInsets.only(right: 16), child: Text('шаг $step из 15'))],
bottom: PreferredSize(
preferredSize: Size(MediaQuery.of(context).size.width, 32),
child: Padding(
padding: const EdgeInsets.all(16),
child: TweenAnimationBuilder<double>(
duration: const Duration(milliseconds: 350),
curve: Curves.easeInOut,
tween: Tween<double>(begin: 1 / 15, end: progressValue),
builder: (context, value, _) => LinearProgressIndicator(
value: value,
borderRadius: BorderRadius.circular(16),
minHeight: 8,
backgroundColor: Colors.grey.shade100,
valueColor: AlwaysStoppedAnimation<Color>(appTheme.blueA400),
),
),
),
),
),
body: PageView(
physics: const NeverScrollableScrollPhysics(),
controller: pageController,
onPageChanged: (index) {
setState(() {
step = index + 1;
progressValue = step / 15;
});
// Report analytics for step completion
RegistrationAnalyticsService.reportRegistrationStep(step);
},
children: [
// LanguageSelectScreen(sheet: state.sheet),
PhoneScreen(sheet: state.sheet),
CredentialsScreen(sheet: state.sheet),
NameScreen(sheet: state.sheet),
GenderScreen(sheet: state.sheet),
DateOfBirthScreen(sheet: state.sheet),
CityOfLivingScreen(sheet: state.sheet),
BodyCircumferencesScreen(sheet: state.sheet),
WeightScreen(sheet: state.sheet),
HeightScreen(sheet: state.sheet),
BloodPressureScreen(sheet: state.sheet),
AllergiesScreen(sheet: state.sheet),
MedicineIntakeScreen(sheet: state.sheet),
FavoriteSportsScreen(sheet: state.sheet),
FavoriteFoodsScreen(sheet: state.sheet),
AdditionalInformationScreen(sheet: state.sheet),
],
),
);
},
),
),
);
}
Future<bool> canGoNext(RegistrationState state, BuildContext context) async {
switch (step) {
case 1:
if (state.sheet.phone.length != 18) {
showDialog(
context: context,
builder: (context) => AlertDialog(
title: const Text('Ошибка'),
content: const Text('Введите корректный номер телефона'),
actions: [TextButton(onPressed: () => Navigator.pop(context), child: const Text('OK'))],
),
);
return false;
} else {
return true;
}
case 2:
List<String> errors = validateCredentialsFields(state.sheet.email, state.sheet.password, state.sheet.passwordConfirmation, context);
if (errors.isEmpty) {
return true;
} else {
String errorMessage = '';
for (var error in errors) {
errorMessage += '$error\n\n';
}
showDialog(
context: context,
builder: (context) => AlertDialog(
title: const Text('Ошибка'),
content: Text(errorMessage),
actions: [TextButton(onPressed: () => Navigator.pop(context), child: const Text('OK'))],
),
);
return false;
}
case 3:
List<String> errors = validateNameFields(state.sheet.firstName.toString(), state.sheet.lastName.toString(), context);
if (errors.isEmpty) {
return true;
} else {
String errorMessage = '';
for (var error in errors) {
errorMessage += '$error\n\n';
}
showDialog(
context: context,
builder: (context) => AlertDialog(
title: const Text('Ошибка'),
content: Text(errorMessage),
actions: [TextButton(onPressed: () => Navigator.pop(context), child: const Text('OK'))],
),
);
return false;
}
case 4:
if (state.sheet.gender.isNotEmpty) {
return true;
} else {
showDialog(
context: context,
builder: (context) => AlertDialog(
title: const Text('Ошибка'),
content: const Text('Выберете пол'),
actions: [TextButton(onPressed: () => Navigator.pop(context), child: const Text('OK'))],
),
);
return false;
}
case 6:
if (state.sheet.location.isNotEmpty) {
return true;
} else {
showDialog(
context: context,
builder: (context) => AlertDialog(
title: const Text('Ошибка'),
content: const Text('Заполните все поля'),
actions: [TextButton(onPressed: () => Navigator.pop(context), child: const Text('OK'))],
),
);
return false;
}
case 7:
List<String> errors = validateCircumferencesFields(
state.sheet.chestCircumference.toString(),
state.sheet.waistCircumference.toString(),
state.sheet.hipCircumference.toString(),
context,
);
if (errors.isEmpty) {
return true;
} else {
String errorMessage = '';
for (var error in errors) {
errorMessage += '$error\n\n';
}
showDialog(
context: context,
builder: (context) => AlertDialog(
title: const Text('Ошибка'),
content: Text(errorMessage),
actions: [TextButton(onPressed: () => Navigator.pop(context), child: const Text('OK'))],
),
);
return false;
}
case 15:
try {
showDialog(
barrierDismissible: false,
useSafeArea: false,
context: context,
builder: (context) => const PopScope(canPop: false, child: Loader()),
);
await registrationCubit.tryRegistration();
if (context.mounted) {
Navigator.pop(context);
}
} catch (e) {
if (context.mounted) {
Navigator.pop(context);
showDialog(
context: context,
builder: (context) => const AlertDialog(title: Text('Ошибка при попытке регистрации')),
);
}
}
return false;
default:
return true;
}
}
List<String> validateCircumferencesFields(String chestCircumference, String waistCircumference, String hipCircumference, BuildContext context) {
final s = S.of(context);
List<String> errors = [];
if (chestCircumference.isEmpty | waistCircumference.isEmpty | hipCircumference.isEmpty) {
errors.add(s.circumference_empty);
}
if (![chestCircumference, waistCircumference, hipCircumference].every((circumference) => int.tryParse(circumference) != null)) {
errors.add(s.circumference_is_not_a_number);
}
return errors;
}
List<String> validateCredentialsFields(String email, String password, String passwordConfirmation, BuildContext context) {
final s = S.of(context);
List<String> errors = [];
if (!isEmail(email)) {
errors.add(s.email_invalid);
}
if (email.isEmpty) {
errors.add(s.email_empty);
}
if (password.length < 6) {
errors.add(s.password_short);
}
if (password != passwordConfirmation) {
errors.add(s.password_confirmation_mismatch);
}
return errors;
}
List<String> validateNameFields(String firstName, String lastName, BuildContext context) {
List<String> errors = [];
if (firstName.isEmpty) {
errors.add('First name cannot be empty');
}
if (firstName.isEmpty) {
errors.add('First name is too short');
}
if (lastName.isEmpty) {
errors.add('Last name cannot be empty');
}
if (lastName.isEmpty) {
errors.add('Last name is too short');
}
return errors;
}
}
import 'package:eatvio_app/core/utils/image_constant.dart';
import 'package:eatvio_app/core/widgets/loader.dart';
import 'package:eatvio_app/features/registration/presentation/bloc/registration_cubit/registration_cubit.dart';
import 'package:eatvio_app/features/registration/presentation/bloc/registration_cubit/registration_state.dart';
import 'package:eatvio_app/features/registration/presentation/pages/additional_information_screen.dart';
import 'package:eatvio_app/features/registration/presentation/pages/allergies_screen.dart';
import 'package:eatvio_app/features/registration/presentation/pages/blood_pressure_screen.dart';
import 'package:eatvio_app/features/registration/presentation/pages/body_circumferences_screen.dart';
import 'package:eatvio_app/features/registration/presentation/pages/city_of_living_screen.dart';
import 'package:eatvio_app/features/registration/presentation/pages/credentials_screen.dart';
import 'package:eatvio_app/features/registration/presentation/pages/date_of_birth_screen.dart';
import 'package:eatvio_app/features/registration/presentation/pages/favorite_foods_screen.dart';
import 'package:eatvio_app/features/registration/presentation/pages/favorite_sports_screen.dart';
import 'package:eatvio_app/features/registration/presentation/pages/gender_screen.dart';
import 'package:eatvio_app/features/registration/presentation/pages/height_screen.dart';
import 'package:eatvio_app/features/registration/presentation/pages/medicine_intake_screen.dart';
import 'package:eatvio_app/features/registration/presentation/pages/name_screen.dart';
import 'package:eatvio_app/features/registration/presentation/pages/phone_screen.dart';
import 'package:eatvio_app/features/registration/presentation/pages/weight_screen.dart';
import 'package:eatvio_app/features/registration/services/registration_analytics_service.dart';
import 'package:eatvio_app/generated/l10n.dart';
import 'package:eatvio_app/core/locator_service.dart';
import 'package:eatvio_app/core/theme/theme_helper.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_svg/flutter_svg.dart';
import 'package:go_router/go_router.dart';
import 'package:validators/validators.dart';
class RegistrationScreen extends StatefulWidget {
const RegistrationScreen({super.key});
@override
State<RegistrationScreen> createState() => _RegistrationScreenState();
}
class _RegistrationScreenState extends State<RegistrationScreen> {
double progressValue = 1 / 15;
int step = 1;
final pageController = PageController();
final RegistrationCubit registrationCubit = sl<RegistrationCubit>();
@override
void initState() {
super.initState();
RegistrationAnalyticsService.reportRegistrationStep(step);
}
@override
Widget build(BuildContext context) {
return BlocProvider<RegistrationCubit>(
create: (BuildContext context) => registrationCubit,
child: BlocListener<RegistrationCubit, RegistrationState>(
listener: (context, state) {
if (state.error != null && state.error!.isNotEmpty) {
showDialog(
context: context,
builder: (context) => AlertDialog(
title: const Text('Ошибка при регистрации'),
content: Text(state.error!),
actions: [TextButton(onPressed: () => Navigator.pop(context), child: const Text('OK'))],
),
);
}
},
child: BlocBuilder<RegistrationCubit, RegistrationState>(
builder: (BuildContext context, RegistrationState state) {
return Scaffold(
floatingActionButtonLocation: FloatingActionButtonLocation.centerFloat,
floatingActionButton: Row(
children: [
Container(
height: 48,
width: MediaQuery.of(context).size.width * 0.2,
padding: const EdgeInsets.only(left: 16, right: 4),
child: FloatingActionButton.extended(
heroTag: 'backButtonRegistration',
highlightElevation: 0,
focusElevation: 0,
elevation: 0,
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(20)),
backgroundColor: const Color(0xFFEAF2FD),
onPressed: () async {
setState(() {
pageController.previousPage(duration: const Duration(milliseconds: 350), curve: Curves.easeInOut);
});
},
label: Icon(Icons.arrow_back_ios_new_rounded, color: appTheme.blueA400),
),
),
Container(
height: 48,
width: MediaQuery.of(context).size.width * 0.8,
padding: const EdgeInsets.only(left: 4, right: 16),
child: FloatingActionButton.extended(
heroTag: 'nextButtonRegistration',
highlightElevation: 0,
elevation: 0,
focusElevation: 0,
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(20)),
backgroundColor: appTheme.blueA400,
onPressed: () async {
if (await canGoNext(state, context)) {
setState(() {
pageController.nextPage(duration: const Duration(milliseconds: 350), curve: Curves.easeInOut);
});
}
},
label: Text('Продолжить', style: theme.textTheme.titleSmall!.copyWith(color: Colors.white)),
),
),
],
),
appBar: AppBar(
automaticallyImplyLeading: false,
leading: Builder(
builder: (context) => IconButton(icon: const Icon(Icons.close_rounded), onPressed: () => GoRouter.of(context).pop()),
),
centerTitle: true,
title: SvgPicture.asset(ImageConstant.imgLogoEatvio, height: 48, fit: BoxFit.contain),
actions: [Padding(padding: const EdgeInsets.only(right: 16), child: Text('шаг $step из 15'))],
bottom: PreferredSize(
preferredSize: Size(MediaQuery.of(context).size.width, 32),
child: Padding(
padding: const EdgeInsets.all(16),
child: TweenAnimationBuilder<double>(
duration: const Duration(milliseconds: 350),
curve: Curves.easeInOut,
tween: Tween<double>(begin: 1 / 15, end: progressValue),
builder: (context, value, _) => LinearProgressIndicator(
value: value,
borderRadius: BorderRadius.circular(16),
minHeight: 8,
backgroundColor: Colors.grey.shade100,
valueColor: AlwaysStoppedAnimation<Color>(appTheme.blueA400),
),
),
),
),
),
body: PageView(
physics: const NeverScrollableScrollPhysics(),
controller: pageController,
onPageChanged: (index) {
setState(() {
step = index + 1;
progressValue = step / 15;
});
// Report analytics for step completion
RegistrationAnalyticsService.reportRegistrationStep(step);
},
children: [
// LanguageSelectScreen(sheet: state.sheet),
PhoneScreen(sheet: state.sheet),
CredentialsScreen(sheet: state.sheet),
NameScreen(sheet: state.sheet),
GenderScreen(sheet: state.sheet),
DateOfBirthScreen(sheet: state.sheet),
CityOfLivingScreen(sheet: state.sheet),
BodyCircumferencesScreen(sheet: state.sheet),
WeightScreen(sheet: state.sheet),
HeightScreen(sheet: state.sheet),
BloodPressureScreen(sheet: state.sheet),
AllergiesScreen(sheet: state.sheet),
MedicineIntakeScreen(sheet: state.sheet),
FavoriteSportsScreen(sheet: state.sheet),
FavoriteFoodsScreen(sheet: state.sheet),
AdditionalInformationScreen(sheet: state.sheet),
],
),
);
},
),
),
);
}
Future<bool> canGoNext(RegistrationState state, BuildContext context) async {
switch (step) {
case 1:
if (state.sheet.phone.length != 18) {
showDialog(
context: context,
builder: (context) => AlertDialog(
title: const Text('Ошибка'),
content: const Text('Введите корректный номер телефона'),
actions: [TextButton(onPressed: () => Navigator.pop(context), child: const Text('OK'))],
),
);
return false;
} else {
return true;
}
case 2:
List<String> errors = validateCredentialsFields(state.sheet.email, state.sheet.password, state.sheet.passwordConfirmation, context);
if (errors.isEmpty) {
return true;
} else {
String errorMessage = '';
for (var error in errors) {
errorMessage += '$error\n\n';
}
showDialog(
context: context,
builder: (context) => AlertDialog(
title: const Text('Ошибка'),
content: Text(errorMessage),
actions: [TextButton(onPressed: () => Navigator.pop(context), child: const Text('OK'))],
),
);
return false;
}
case 3:
List<String> errors = validateNameFields(state.sheet.firstName.toString(), state.sheet.lastName.toString(), context);
if (errors.isEmpty) {
return true;
} else {
String errorMessage = '';
for (var error in errors) {
errorMessage += '$error\n\n';
}
showDialog(
context: context,
builder: (context) => AlertDialog(
title: const Text('Ошибка'),
content: Text(errorMessage),
actions: [TextButton(onPressed: () => Navigator.pop(context), child: const Text('OK'))],
),
);
return false;
}
case 4:
if (state.sheet.gender.isNotEmpty) {
return true;
} else {
showDialog(
context: context,
builder: (context) => AlertDialog(
title: const Text('Ошибка'),
content: const Text('Выберете пол'),
actions: [TextButton(onPressed: () => Navigator.pop(context), child: const Text('OK'))],
),
);
return false;
}
case 6:
if (state.sheet.location.isNotEmpty) {
return true;
} else {
showDialog(
context: context,
builder: (context) => AlertDialog(
title: const Text('Ошибка'),
content: const Text('Заполните все поля'),
actions: [TextButton(onPressed: () => Navigator.pop(context), child: const Text('OK'))],
),
);
return false;
}
case 7:
List<String> errors = validateCircumferencesFields(
state.sheet.chestCircumference.toString(),
state.sheet.waistCircumference.toString(),
state.sheet.hipCircumference.toString(),
context,
);
if (errors.isEmpty) {
return true;
} else {
String errorMessage = '';
for (var error in errors) {
errorMessage += '$error\n\n';
}
showDialog(
context: context,
builder: (context) => AlertDialog(
title: const Text('Ошибка'),
content: Text(errorMessage),
actions: [TextButton(onPressed: () => Navigator.pop(context), child: const Text('OK'))],
),
);
return false;
}
case 15:
try {
showDialog(
barrierDismissible: false,
useSafeArea: false,
context: context,
builder: (context) => const PopScope(canPop: false, child: Loader()),
);
await registrationCubit.tryRegistration();
if (context.mounted) {
Navigator.pop(context);
}
} catch (e) {
if (context.mounted) {
Navigator.pop(context);
String errorMessage = '';
if (e is String) {
errorMessage = e;
} else {
errorMessage = e.toString().replaceFirst('Exception: ', '').replaceFirst('Error: ', '');
}
showDialog(
context: context,
builder: (context) => AlertDialog(
title: const Text('Ошибка при регистрации'),
content: Text(errorMessage.isNotEmpty ? errorMessage : 'Регистрация не удалась'),
actions: [TextButton(onPressed: () => Navigator.pop(context), child: const Text('OK'))],
),
);
}
}
return false;
default:
return true;
}
}
List<String> validateCircumferencesFields(String chestCircumference, String waistCircumference, String hipCircumference, BuildContext context) {
final s = S.of(context);
List<String> errors = [];
if (chestCircumference.isEmpty | waistCircumference.isEmpty | hipCircumference.isEmpty) {
errors.add(s.circumference_empty);
}
if (![chestCircumference, waistCircumference, hipCircumference].every((circumference) => int.tryParse(circumference) != null)) {
errors.add(s.circumference_is_not_a_number);
}
return errors;
}
List<String> validateCredentialsFields(String email, String password, String passwordConfirmation, BuildContext context) {
final s = S.of(context);
List<String> errors = [];
if (!isEmail(email)) {
errors.add(s.email_invalid);
}
if (email.isEmpty) {
errors.add(s.email_empty);
}
if (password.length < 6) {
errors.add(s.password_short);
}
if (password != passwordConfirmation) {
errors.add(s.password_confirmation_mismatch);
}
return errors;
}
List<String> validateNameFields(String firstName, String lastName, BuildContext context) {
List<String> errors = [];
if (firstName.isEmpty) {
errors.add('First name cannot be empty');
}
if (firstName.isEmpty) {
errors.add('First name is too short');
}
if (lastName.isEmpty) {
errors.add('Last name cannot be empty');
}
if (lastName.isEmpty) {
errors.add('Last name is too short');
}
return errors;
}
}

View File

@@ -1,238 +1,266 @@
import 'dart:developer';
import 'package:dio/dio.dart';
import 'package:eatvio_app/core/token_storage.dart';
import 'package:eatvio_app/core/utils/date_time_utils.dart';
import 'package:eatvio_app/features/registration_v2/cubit/registration_state.dart';
import 'package:eatvio_app/features/registration_v2/models/registration_model.dart';
import 'package:eatvio_app/features/registration_v2/repositories/registration_repository_v2.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:gpassword/gpassword.dart';
class RegistrationCubitV2 extends Cubit<RegistrationStateV2> {
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<String, dynamic>?;
final emailErrors = errors?['email'] as List<dynamic>?;
final phoneErrors = errors?['phone'] as List<dynamic>?;
final dateOfBirth = errors?['date_of_birth'] as List<dynamic>?;
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<String> _generatePassword() async {
final generator = GPassword();
final password = generator.generate(passwordLength: 10);
emit(state.copyWith(password: password));
return password;
}
}
import 'dart:developer';
import 'package:dio/dio.dart';
import 'package:eatvio_app/core/token_storage.dart';
import 'package:eatvio_app/core/utils/date_time_utils.dart';
import 'package:eatvio_app/features/registration_v2/cubit/registration_state.dart';
import 'package:eatvio_app/features/registration_v2/models/registration_model.dart';
import 'package:eatvio_app/features/registration_v2/repositories/registration_repository_v2.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:gpassword/gpassword.dart';
class RegistrationCubitV2 extends Cubit<RegistrationStateV2> {
final RegistrationRepositoryV2 registrationRepository;
RegistrationCubitV2({required this.registrationRepository}) : super(const RegistrationStateV2());
void setFirstName(String firstName) {
emit(state.copyWith(firstName: firstName));
validateFirstName();
}
void setLastName(String lastName) {
emit(state.copyWith(lastName: lastName));
validateLastName();
}
void setEmail(String email) {
emit(state.copyWith(email: email));
validateEmail();
}
void setPhone(String phone) {
emit(state.copyWith(phone: phone));
validatePhone();
}
void setBirthday(DateTime birthday) {
emit(state.copyWith(birthday: birthday));
validateBirthday();
}
void setGender(String gender) {
emit(state.copyWith(gender: gender));
validateGender();
}
void setWeight(int weight) {
emit(state.copyWith(weight: weight));
validateWeight();
}
void setHeight(int height) {
emit(state.copyWith(height: height));
validateHeight();
}
void setActivityCoefficient(double activityCoefficient) {
emit(state.copyWith(activityCoefficient: activityCoefficient));
validateActivityCoefficient();
}
void setPassword(String password) {
emit(state.copyWith(password: password));
}
bool validateFirstName() {
if (state.firstName.isEmpty) {
emit(state.copyWith(isFirstNameValid: false));
return false;
}
emit(state.copyWith(isFirstNameValid: true));
return true;
}
bool validateLastName() {
if (state.lastName.isEmpty) {
emit(state.copyWith(isLastNameValid: false));
return false;
}
emit(state.copyWith(isLastNameValid: true));
return true;
}
bool validateEmail() {
if (state.email.isEmpty) {
emit(state.copyWith(isEmailValid: false));
return false;
}
emit(state.copyWith(isEmailValid: true));
return true;
}
bool validatePhone() {
if (state.phone.isEmpty) {
emit(state.copyWith(isPhoneValid: false));
return false;
}
emit(state.copyWith(isPhoneValid: true));
return true;
}
bool validateGender() {
if (state.gender.isEmpty) {
emit(state.copyWith(isGenderValid: false));
return false;
}
emit(state.copyWith(isGenderValid: true));
return true;
}
bool validateWeight() {
if (state.weight == 0) {
emit(state.copyWith(isWeightValid: false));
return false;
}
emit(state.copyWith(isWeightValid: true));
return true;
}
bool validateHeight() {
if (state.height == 0) {
emit(state.copyWith(isHeightValid: false));
return false;
}
emit(state.copyWith(isHeightValid: true));
return true;
}
bool validateActivityCoefficient() {
if (state.activityCoefficient == 0) {
emit(state.copyWith(isActivityCoefficientValid: false));
return false;
}
emit(state.copyWith(isActivityCoefficientValid: true));
return true;
}
bool validateBirthday() {
if (state.birthday == null) {
emit(state.copyWith(isBirthdayValid: false));
return false;
}
emit(state.copyWith(isBirthdayValid: true));
return true;
}
void nextStep() {
emit(state.copyWith(step: 1));
}
void prevStep() {
emit(state.copyWith(step: 0));
}
bool validateAllFields() {
if (!validateFirstName() ||
!validateLastName() ||
!validateEmail() ||
!validatePhone() ||
!validateGender() ||
!validateWeight() ||
!validateHeight() ||
!validateActivityCoefficient() ||
!validateBirthday()) {
return false;
}
return true;
}
Future<(bool, bool)> register() async {
if (!validateAllFields()) {
return (false, true);
}
final birthday = state.birthday?.format('yyyy-MM-dd');
if (birthday == null) {
validateBirthday();
return (false, true);
}
final password = await _generatePassword();
final registrationModel = RegistrationModel(
firstName: state.firstName,
lastName: state.lastName,
email: state.email,
phone: state.phone,
gender: state.gender,
password: password,
confirmPassword: password,
weight: state.weight,
height: state.height,
activityCoefficient: state.activityCoefficient,
birthday: birthday,
);
bool didApiComplainOnValidation = false;
String errorMessage = '';
try {
final token = await registrationRepository.register(registrationModel: registrationModel);
if (token != null) {
TokenStorage.saveTokenInStorage(token);
emit(state.copyWith(registrationError: null));
return (true, false);
}
} on DioException catch (e) {
final errors = e.response?.data['errors'] as Map<String, dynamic>?;
final emailErrors = errors?['email'] as List<dynamic>?;
final phoneErrors = errors?['phone'] as List<dynamic>?;
final dateOfBirth = errors?['date_of_birth'] as List<dynamic>?;
// Собираем все ошибки для отображения в диалоге
List<String> allErrors = [];
if (emailErrors != null && emailErrors.isNotEmpty) {
for (var error in emailErrors) {
allErrors.add(error.toString());
if (error.toString() == 'Некорректный формат email') {
emit(state.copyWith(isEmailValid: false, emailDisplayError: 'Некорректный формат email'));
didApiComplainOnValidation = true;
}
if (error.toString() == 'Такой Эл. почта уже существует.') {
emit(state.copyWith(isEmailValid: false, emailDisplayError: 'Такая Эл. почта уже существует.'));
didApiComplainOnValidation = true;
}
}
}
if (phoneErrors != null && phoneErrors.isNotEmpty) {
for (var error in phoneErrors) {
allErrors.add(error.toString());
if (error.toString() == 'Такой phone уже существует.') {
emit(state.copyWith(isPhoneValid: false, phoneDisplayError: 'Такой номер телефона уже существует.'));
didApiComplainOnValidation = true;
}
}
}
if (dateOfBirth != null && dateOfBirth.isNotEmpty) {
for (var error in dateOfBirth) {
allErrors.add(error.toString());
if (error.toString() == 'Некорректный формат даты рождения') {
emit(state.copyWith(isBirthdayValid: false, dateOfBirthDisplayError: 'Некорректный формат даты рождения'));
didApiComplainOnValidation = true;
}
}
}
// Сохраняем все ошибки для отображения в диалоге
if (allErrors.isNotEmpty) {
errorMessage = allErrors.join('\n');
emit(state.copyWith(registrationError: errorMessage));
} else {
// Если нет конкретных ошибок, берем общее сообщение
final message = e.response?.data['message'] as String?;
errorMessage = message ?? 'Ошибка при регистрации';
emit(state.copyWith(registrationError: errorMessage));
}
} catch (e) {
log(e.toString());
errorMessage = e.toString();
emit(state.copyWith(registrationError: errorMessage));
}
return (false, didApiComplainOnValidation);
}
Future<String> _generatePassword() async {
final generator = GPassword();
final password = generator.generate(passwordLength: 10);
emit(state.copyWith(password: password));
return password;
}
}

View File

@@ -1,32 +1,33 @@
import 'package:freezed_annotation/freezed_annotation.dart';
part 'registration_state.freezed.dart';
@freezed
abstract class RegistrationStateV2 with _$RegistrationStateV2 {
factory RegistrationStateV2({
@Default('') String firstName,
@Default('') String lastName,
@Default('') String email,
@Default('') String phone,
@Default('') String gender,
@Default('') String password,
@Default(0) int weight,
@Default(0) int height,
@Default(1.2) double activityCoefficient,
DateTime? birthday,
@Default(true) bool isFirstNameValid,
@Default(true) bool isLastNameValid,
@Default(true) bool isEmailValid,
@Default(true) bool isPhoneValid,
@Default(true) bool isGenderValid,
@Default(true) bool isWeightValid,
@Default(true) bool isHeightValid,
@Default(true) bool isActivityCoefficientValid,
@Default(true) bool isBirthdayValid,
@Default(0) int step,
@Default('') String emailDisplayError,
@Default('') String phoneDisplayError,
@Default('') String dateOfBirthDisplayError,
}) = _RegistrationStateV2;
}
import 'package:freezed_annotation/freezed_annotation.dart';
part 'registration_state.freezed.dart';
@freezed
abstract class RegistrationStateV2 with _$RegistrationStateV2 {
const factory RegistrationStateV2({
@Default('') String firstName,
@Default('') String lastName,
@Default('') String email,
@Default('') String phone,
@Default('') String gender,
@Default('') String password,
@Default(0) int weight,
@Default(0) int height,
@Default(1.2) double activityCoefficient,
DateTime? birthday,
@Default(true) bool isFirstNameValid,
@Default(true) bool isLastNameValid,
@Default(true) bool isEmailValid,
@Default(true) bool isPhoneValid,
@Default(true) bool isGenderValid,
@Default(true) bool isWeightValid,
@Default(true) bool isHeightValid,
@Default(true) bool isActivityCoefficientValid,
@Default(true) bool isBirthdayValid,
@Default(0) int step,
@Default('') String emailDisplayError,
@Default('') String phoneDisplayError,
@Default('') String dateOfBirthDisplayError,
String? registrationError,
}) = _RegistrationStateV2;
}

View File

@@ -137,7 +137,8 @@ class _SecondStepContentState extends State<SecondStepContent> {
setState(() {
isLoading = false;
});
if (context.mounted && !hasValidationError) {
if (context.mounted) {
// Показываем bottom sheet всегда: при успехе или ошибке
showRegistrationBottomSheet(context, cubit, commonResult);
}
},
@@ -157,7 +158,7 @@ class _SecondStepContentState extends State<SecondStepContent> {
child: RichText(
textAlign: TextAlign.center,
text: TextSpan(
text: 'Нажимая “Зарегистрироваться”, Вы соглашаетесь \n',
text: 'Нажимая "Зарегистрироваться", Вы соглашаетесь \n',
style: const TextStyle(fontSize: 12, color: Colors.black),
children: [
const TextSpan(text: 'c '),

View File

@@ -76,7 +76,7 @@ class _RegistrationBottomSheetState extends State<RegistrationBottomSheet> {
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.verifyEmailScreen);
AppRouter.navigatorKey.currentContext?.go(RouteConstants.feedScreen);
},
),
const SizedBox(width: 20),
@@ -96,7 +96,12 @@ class _RegistrationBottomSheetState extends State<RegistrationBottomSheet> {
Padding(
padding: const EdgeInsets.only(bottom: 16.0),
child: Text(s.error_desc, style: TextStyles.textMMediumAuto.copyWith(color: appTheme.darkGrey)),
child: Text(
widget.cubit.state.registrationError?.isNotEmpty == true
? widget.cubit.state.registrationError!
: s.error_desc,
style: TextStyles.textMMediumAuto.copyWith(color: appTheme.darkGrey),
),
),
const SizedBox(width: 20),
],

View File

@@ -1,6 +1,6 @@
name: eatvio_app
description: A new Flutter project.
version: 1.3.10+90
version: 1.3.11+91
publish_to: 'none'
environment:

View File

@@ -151,6 +151,9 @@ void main() {

View File

@@ -86,6 +86,9 @@ void main() {

View File

@@ -100,6 +100,9 @@ void main() {

View File

@@ -49,6 +49,9 @@ void main() {

View File

@@ -45,6 +45,9 @@ void main() {

View File

@@ -42,6 +42,9 @@ void main() {

View File

@@ -125,6 +125,9 @@ void main() {

View File

@@ -49,6 +49,9 @@ void main() {

View File

@@ -43,6 +43,9 @@ void main() {

View File

@@ -68,6 +68,9 @@ void main() {

View File

@@ -150,6 +150,9 @@ void main() {

View File

@@ -154,6 +154,9 @@ void main() {

View File

@@ -91,6 +91,9 @@ void main() {

View File

@@ -80,6 +80,9 @@ void main() {

View File

@@ -73,6 +73,9 @@ void main() {

View File

@@ -105,6 +105,9 @@ void main() {

View File

@@ -66,6 +66,9 @@ void main() {

View File

@@ -77,5 +77,8 @@ void main() {