forked from eatvio/eatvio_app
chore: update version to 1.3.11+91 in pubspec.yaml; clean up whitespace in various files
This commit is contained in:
@@ -31,3 +31,6 @@ abstract class OAuthVerificationConfirmResponse with _$OAuthVerificationConfirmR
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -25,3 +25,6 @@ abstract class OAuthVerificationRequestResponse with _$OAuthVerificationRequestR
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -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 ? '' : 'Войдите в аккаунт'}');
|
||||
}
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,3 +19,6 @@ abstract class BarcodeService {
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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 '),
|
||||
|
||||
@@ -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),
|
||||
],
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -151,6 +151,9 @@ void main() {
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -86,6 +86,9 @@ void main() {
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -100,6 +100,9 @@ void main() {
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -49,6 +49,9 @@ void main() {
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -75,6 +75,9 @@ void main() {
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -45,6 +45,9 @@ void main() {
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -42,6 +42,9 @@ void main() {
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -125,6 +125,9 @@ void main() {
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -49,6 +49,9 @@ void main() {
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -43,6 +43,9 @@ void main() {
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -68,6 +68,9 @@ void main() {
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -150,6 +150,9 @@ void main() {
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -154,6 +154,9 @@ void main() {
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -91,6 +91,9 @@ void main() {
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -80,6 +80,9 @@ void main() {
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -95,6 +95,9 @@ void main() {
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -73,6 +73,9 @@ void main() {
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -105,6 +105,9 @@ void main() {
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -57,6 +57,9 @@ void main() {
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -73,6 +73,9 @@ void main() {
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -66,6 +66,9 @@ void main() {
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -1043,6 +1043,9 @@ void main() {
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -77,5 +77,8 @@ void main() {
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user