forked from eatvio/eatvio_app
379 lines
15 KiB
Dart
379 lines
15 KiB
Dart
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;
|
|
}
|
|
}
|