diff --git a/lib/features/registration/presentation/bloc/registration_cubit/registration_cubit.dart b/lib/features/registration/presentation/bloc/registration_cubit/registration_cubit.dart index c9426674..ccf9cae4 100644 --- a/lib/features/registration/presentation/bloc/registration_cubit/registration_cubit.dart +++ b/lib/features/registration/presentation/bloc/registration_cubit/registration_cubit.dart @@ -8,62 +8,61 @@ import 'package:eatvio_app/core/token_storage.dart'; import 'package:eatvio_app/features/registration/models/user_registration_model.dart'; import 'package:eatvio_app/features/registration/presentation/bloc/registration_cubit/registration_state.dart'; import 'package:eatvio_app/features/registration/repositories/registration_repository.dart'; +import 'package:eatvio_app/features/registration/services/registration_analytics_service.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:go_router/go_router.dart'; class RegistrationCubit extends Cubit { final RegistrationRepository registrationRepository; - RegistrationCubit({ - required this.registrationRepository, - }) : super( - const RegistrationState( - sheet: UserRegistrationSheet( - firstName: '', - lastName: '', - email: '', - password: '', - passwordConfirmation: '', - birthdate: '', - gender: '', - location: '', - responseBody: '{}', - lang: 'Русский', - weight: 0, - height: 0, - chestCircumference: 0, - waistCircumference: 0, - hipCircumference: 0, - bloodPressureUp: 0, - bloodPressureDown: 0, - hasHypo: 0, - hasHyper: 0, - aboutAllergy: '', - aboutTakingPils: '', - aboutSportHobbies: '', - favoriteFoods: '', - unfavoriteFoods: '', - additionalInfo: '', - phone: '', - ), + RegistrationCubit({required this.registrationRepository}) + : super( + const RegistrationState( + sheet: UserRegistrationSheet( + firstName: '', + lastName: '', + email: '', + password: '', + passwordConfirmation: '', + birthdate: '', + gender: '', + location: '', + responseBody: '{}', + lang: 'Русский', + weight: 0, + height: 0, + chestCircumference: 0, + waistCircumference: 0, + hipCircumference: 0, + bloodPressureUp: 0, + bloodPressureDown: 0, + hasHypo: 0, + hasHyper: 0, + aboutAllergy: '', + aboutTakingPils: '', + aboutSportHobbies: '', + favoriteFoods: '', + unfavoriteFoods: '', + additionalInfo: '', + phone: '', ), - ); + ), + ); void updateSheet(UserRegistrationSheet sheet) => emit(RegistrationState(sheet: sheet)); Future tryRegistration() async { try { final token = await registrationRepository.tryRegister( - state.sheet.copyWith( - phone: state.sheet.phone.replaceAll(' ', '').replaceAll('(', '').replaceAll(')', ''), - responseBody: '{}', - ), + state.sheet.copyWith(phone: state.sheet.phone.replaceAll(' ', '').replaceAll('(', '').replaceAll(')', ''), responseBody: '{}'), ); await TokenStorage.saveTokenInStorage(token); await sl().registerPushToken(); + RegistrationAnalyticsService.reportRegistrationComplete(); AppRouter.navigatorKey.currentContext?.replaceNamed('verifyEmailScreen'); } catch (e, st) { log(e.toString(), stackTrace: st); + RegistrationAnalyticsService.reportRegistrationError(e.toString()); emit(RegistrationState(sheet: state.sheet, error: e.toString())); } } diff --git a/lib/features/registration/presentation/bloc/verify_email_cubit/verify_email_cubit.dart b/lib/features/registration/presentation/bloc/verify_email_cubit/verify_email_cubit.dart index 649c1fb8..d1f547f8 100644 --- a/lib/features/registration/presentation/bloc/verify_email_cubit/verify_email_cubit.dart +++ b/lib/features/registration/presentation/bloc/verify_email_cubit/verify_email_cubit.dart @@ -1,13 +1,12 @@ import 'package:eatvio_app/features/profile/repositories/profile_repository.dart'; import 'package:eatvio_app/features/registration/presentation/bloc/verify_email_cubit/verify_email_state.dart'; +import 'package:eatvio_app/features/registration/services/registration_analytics_service.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; class VerifyEmailCubit extends Cubit { final ProfileRepository profileRepository; - VerifyEmailCubit({ - required this.profileRepository, - }) : super(const VerifyEmailState.loading()); + VerifyEmailCubit({required this.profileRepository}) : super(const VerifyEmailState.loading()); Future sendEmailCode() async { try { @@ -24,6 +23,7 @@ class VerifyEmailCubit extends Cubit { emit(const VerifyEmailState.verifyLoading()); final result = await profileRepository.tryVerifyEmailByCode(code); if (result == true) { + RegistrationAnalyticsService.reportEmailVerification(); emit(const VerifyEmailState.verifySuccess()); } else { emit(const VerifyEmailState.verifyError()); diff --git a/lib/features/registration/presentation/pages/registration_screen.dart b/lib/features/registration/presentation/pages/registration_screen.dart index 86514be8..9f30ca33 100644 --- a/lib/features/registration/presentation/pages/registration_screen.dart +++ b/lib/features/registration/presentation/pages/registration_screen.dart @@ -17,6 +17,7 @@ import 'package:eatvio_app/features/registration/presentation/pages/medicine_int 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'; @@ -51,12 +52,7 @@ class _RegistrationScreenState extends State { builder: (context) => AlertDialog( title: const Text('Ошибка при регистрации'), content: Text(state.error!), - actions: [ - TextButton( - onPressed: () => Navigator.pop(context), - child: const Text('OK'), - ), - ], + actions: [TextButton(onPressed: () => Navigator.pop(context), child: const Text('OK'))], ), ); } @@ -76,22 +72,14 @@ class _RegistrationScreenState extends State { highlightElevation: 0, focusElevation: 0, elevation: 0, - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(20), - ), + shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(20)), backgroundColor: const Color(0xFFEAF2FD), onPressed: () async { setState(() { - pageController.previousPage( - duration: const Duration(milliseconds: 350), - curve: Curves.easeInOut, - ); + pageController.previousPage(duration: const Duration(milliseconds: 350), curve: Curves.easeInOut); }); }, - label: Icon( - Icons.arrow_back_ios_new_rounded, - color: appTheme.blueA400, - ), + label: Icon(Icons.arrow_back_ios_new_rounded, color: appTheme.blueA400), ), ), Container( @@ -103,24 +91,16 @@ class _RegistrationScreenState extends State { highlightElevation: 0, elevation: 0, focusElevation: 0, - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(20), - ), + 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, - ); + pageController.nextPage(duration: const Duration(milliseconds: 350), curve: Curves.easeInOut); }); } }, - label: Text( - 'Продолжить', - style: theme.textTheme.titleSmall!.copyWith(color: Colors.white), - ), + label: Text('Продолжить', style: theme.textTheme.titleSmall!.copyWith(color: Colors.white)), ), ), ], @@ -128,23 +108,11 @@ class _RegistrationScreenState extends State { appBar: AppBar( automaticallyImplyLeading: false, leading: Builder( - builder: (context) => IconButton( - icon: const Icon(Icons.close_rounded), - onPressed: () => GoRouter.of(context).pop(), - ), + 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'), - ), - ], + 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( @@ -152,10 +120,7 @@ class _RegistrationScreenState extends State { child: TweenAnimationBuilder( duration: const Duration(milliseconds: 350), curve: Curves.easeInOut, - tween: Tween( - begin: 1 / 15, - end: progressValue, - ), + tween: Tween(begin: 1 / 15, end: progressValue), builder: (context, value, _) => LinearProgressIndicator( value: value, borderRadius: BorderRadius.circular(16), @@ -175,6 +140,8 @@ class _RegistrationScreenState extends State { step = index + 1; progressValue = step / 15; }); + // Report analytics for step completion + RegistrationAnalyticsService.reportRegistrationStep(step); }, children: [ // LanguageSelectScreen(sheet: state.sheet), @@ -211,12 +178,7 @@ class _RegistrationScreenState extends State { builder: (context) => AlertDialog( title: const Text('Ошибка'), content: const Text('Введите корректный номер телефона'), - actions: [ - TextButton( - onPressed: () => Navigator.pop(context), - child: const Text('OK'), - ), - ], + actions: [TextButton(onPressed: () => Navigator.pop(context), child: const Text('OK'))], ), ); return false; @@ -224,12 +186,7 @@ class _RegistrationScreenState extends State { return true; } case 2: - List errors = validateCredentialsFields( - state.sheet.email, - state.sheet.password, - state.sheet.passwordConfirmation, - context, - ); + List errors = validateCredentialsFields(state.sheet.email, state.sheet.password, state.sheet.passwordConfirmation, context); if (errors.isEmpty) { return true; } else { @@ -244,22 +201,13 @@ class _RegistrationScreenState extends State { builder: (context) => AlertDialog( title: const Text('Ошибка'), content: Text(errorMessage), - actions: [ - TextButton( - onPressed: () => Navigator.pop(context), - child: const Text('OK'), - ), - ], + actions: [TextButton(onPressed: () => Navigator.pop(context), child: const Text('OK'))], ), ); return false; } case 3: - List errors = validateNameFields( - state.sheet.firstName.toString(), - state.sheet.lastName.toString(), - context, - ); + List errors = validateNameFields(state.sheet.firstName.toString(), state.sheet.lastName.toString(), context); if (errors.isEmpty) { return true; } else { @@ -274,12 +222,7 @@ class _RegistrationScreenState extends State { builder: (context) => AlertDialog( title: const Text('Ошибка'), content: Text(errorMessage), - actions: [ - TextButton( - onPressed: () => Navigator.pop(context), - child: const Text('OK'), - ), - ], + actions: [TextButton(onPressed: () => Navigator.pop(context), child: const Text('OK'))], ), ); return false; @@ -293,12 +236,7 @@ class _RegistrationScreenState extends State { builder: (context) => AlertDialog( title: const Text('Ошибка'), content: const Text('Выберете пол'), - actions: [ - TextButton( - onPressed: () => Navigator.pop(context), - child: const Text('OK'), - ), - ], + actions: [TextButton(onPressed: () => Navigator.pop(context), child: const Text('OK'))], ), ); return false; @@ -312,12 +250,7 @@ class _RegistrationScreenState extends State { builder: (context) => AlertDialog( title: const Text('Ошибка'), content: const Text('Заполните все поля'), - actions: [ - TextButton( - onPressed: () => Navigator.pop(context), - child: const Text('OK'), - ), - ], + actions: [TextButton(onPressed: () => Navigator.pop(context), child: const Text('OK'))], ), ); return false; @@ -343,12 +276,7 @@ class _RegistrationScreenState extends State { builder: (context) => AlertDialog( title: const Text('Ошибка'), content: Text(errorMessage), - actions: [ - TextButton( - onPressed: () => Navigator.pop(context), - child: const Text('OK'), - ), - ], + actions: [TextButton(onPressed: () => Navigator.pop(context), child: const Text('OK'))], ), ); return false; @@ -359,10 +287,7 @@ class _RegistrationScreenState extends State { barrierDismissible: false, useSafeArea: false, context: context, - builder: (context) => const PopScope( - canPop: false, - child: Loader(), - ), + builder: (context) => const PopScope(canPop: false, child: Loader()), ); await registrationCubit.tryRegistration(); if (context.mounted) { @@ -373,9 +298,7 @@ class _RegistrationScreenState extends State { Navigator.pop(context); showDialog( context: context, - builder: (context) => const AlertDialog( - title: Text('Ошибка при попытке регистрации'), - ), + builder: (context) => const AlertDialog(title: Text('Ошибка при попытке регистрации')), ); } } @@ -385,12 +308,7 @@ class _RegistrationScreenState extends State { } } - List validateCircumferencesFields( - String chestCircumference, - String waistCircumference, - String hipCircumference, - BuildContext context, - ) { + List validateCircumferencesFields(String chestCircumference, String waistCircumference, String hipCircumference, BuildContext context) { final s = S.of(context); List errors = []; @@ -406,12 +324,7 @@ class _RegistrationScreenState extends State { return errors; } - List validateCredentialsFields( - String email, - String password, - String passwordConfirmation, - BuildContext context, - ) { + List validateCredentialsFields(String email, String password, String passwordConfirmation, BuildContext context) { final s = S.of(context); List errors = []; @@ -435,11 +348,7 @@ class _RegistrationScreenState extends State { return errors; } - List validateNameFields( - String firstName, - String lastName, - BuildContext context, - ) { + List validateNameFields(String firstName, String lastName, BuildContext context) { List errors = []; if (firstName.isEmpty) { diff --git a/lib/features/registration/presentation/pages/verify_email_screen.dart b/lib/features/registration/presentation/pages/verify_email_screen.dart index f50d4960..e91b9e9e 100644 --- a/lib/features/registration/presentation/pages/verify_email_screen.dart +++ b/lib/features/registration/presentation/pages/verify_email_screen.dart @@ -7,6 +7,7 @@ import 'package:eatvio_app/core/utils/toasts.dart'; import 'package:eatvio_app/core/widgets/custom_outlined_button.dart'; import 'package:eatvio_app/features/registration/presentation/bloc/verify_email_cubit/verify_email_cubit.dart'; import 'package:eatvio_app/features/registration/presentation/bloc/verify_email_cubit/verify_email_state.dart'; +import 'package:eatvio_app/features/registration/services/registration_analytics_service.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:go_router/go_router.dart'; @@ -29,8 +30,10 @@ class _VerifyEmailScreenState extends State { @override void initState() { super.initState(); - goRouter = GoRouter.of(context); + goRouter = GoRouter.of(context); _startTimer(); + // Report email verification screen opened + RegistrationAnalyticsService.reportRegistrationStep(16); // Email verification step } @override diff --git a/lib/features/registration/presentation/pages/verify_screen.dart b/lib/features/registration/presentation/pages/verify_screen.dart index 637dba5b..8d5df9df 100644 --- a/lib/features/registration/presentation/pages/verify_screen.dart +++ b/lib/features/registration/presentation/pages/verify_screen.dart @@ -1,6 +1,7 @@ import 'package:eatvio_app/core/const.dart'; import 'package:eatvio_app/core/theme/theme_helper.dart'; import 'package:eatvio_app/core/widgets/loader.dart'; +import 'package:eatvio_app/features/registration/services/registration_analytics_service.dart'; import 'package:flutter/material.dart'; import 'package:go_router/go_router.dart'; import 'package:lottie/lottie.dart'; @@ -9,10 +10,7 @@ import 'package:webview_flutter/webview_flutter.dart'; class VerifyScreen extends StatefulWidget { final String passKey; - const VerifyScreen({ - super.key, - required this.passKey, - }); + const VerifyScreen({super.key, required this.passKey}); @override State createState() => _VerifyScreenState(); @@ -25,6 +23,8 @@ class _VerifyScreenState extends State { @override void initState() { super.initState(); + // Report phone verification screen opened + RegistrationAnalyticsService.reportRegistrationStep(17); // Phone verification step _controller = WebViewController() ..setJavaScriptMode(JavaScriptMode.unrestricted) ..setNavigationDelegate( @@ -36,6 +36,8 @@ class _VerifyScreenState extends State { }, onNavigationRequest: (NavigationRequest request) async { if (request.url.contains('user_detailz') && !request.url.contains('change-phone-verify') || request.url.contains('email/verify')) { + // Report phone verification success + RegistrationAnalyticsService.reportPhoneVerification(); GoRouter.of(context).pop(); await showModalBottomSheet( useRootNavigator: true, @@ -50,27 +52,15 @@ class _VerifyScreenState extends State { const Text( 'Номер телефона\nуспешно привязан', textAlign: TextAlign.center, - style: TextStyle( - fontSize: 20, - height: 1, - fontWeight: FontWeight.w600, - ), + style: TextStyle(fontSize: 20, height: 1, fontWeight: FontWeight.w600), ), const SizedBox(height: 16), - Lottie.asset( - 'assets/lottie_animations/success.json', - width: 160, - height: 160, - ), + Lottie.asset('assets/lottie_animations/success.json', width: 160, height: 160), const SizedBox(height: 16), const Text( 'Теперь Вам доступен весь функционал!', textAlign: TextAlign.center, - style: TextStyle( - fontSize: 16, - height: 1, - fontWeight: FontWeight.w500, - ), + style: TextStyle(fontSize: 16, height: 1, fontWeight: FontWeight.w500), ), const SizedBox(height: 16), InkWell( @@ -80,18 +70,11 @@ class _VerifyScreenState extends State { child: Ink( width: MediaQuery.of(context).size.width - 32, height: 52, - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(20), - color: appTheme.blueA400, - ), + decoration: BoxDecoration(borderRadius: BorderRadius.circular(20), color: appTheme.blueA400), child: const Center( child: Text( 'Закрыть', - style: TextStyle( - color: Colors.white, - fontSize: 18, - fontWeight: FontWeight.w600, - ), + style: TextStyle(color: Colors.white, fontSize: 18, fontWeight: FontWeight.w600), ), ), ), @@ -117,10 +100,7 @@ class _VerifyScreenState extends State { child: Stack( children: [ WebViewWidget(controller: _controller), - if (_isLoading) - const Center( - child: Loader(), - ), + if (_isLoading) const Center(child: Loader()), ], ), ), diff --git a/lib/features/registration/presentation/pages/webview_registration_screen.dart b/lib/features/registration/presentation/pages/webview_registration_screen.dart index abb6537d..05c0ffc1 100644 --- a/lib/features/registration/presentation/pages/webview_registration_screen.dart +++ b/lib/features/registration/presentation/pages/webview_registration_screen.dart @@ -4,6 +4,7 @@ import 'dart:developer'; import 'package:eatvio_app/core/const.dart'; import 'package:eatvio_app/core/locator_service.dart'; import 'package:eatvio_app/features/auth/presentation/bloc/auth_cubit/auth_cubit.dart'; +import 'package:eatvio_app/features/registration/services/registration_analytics_service.dart'; import 'package:flutter/material.dart'; import 'package:webview_flutter/webview_flutter.dart'; @@ -24,6 +25,8 @@ class _WebViewRegistrationScreenState extends State { void initState() { super.initState(); authCubit = sl(); + // Report webview registration started + RegistrationAnalyticsService.reportRegistrationStep(18); // Webview registration step _controller = WebViewController() ..setJavaScriptMode(JavaScriptMode.unrestricted) ..addJavaScriptChannel( @@ -43,10 +46,9 @@ class _WebViewRegistrationScreenState extends State { NavigationDelegate( onNavigationRequest: (NavigationRequest request) { if (request.url.contains('profile')) { - authCubit.getTokenByCredentials( - email!, - password!, - ); + // Report webview registration completion + RegistrationAnalyticsService.reportRegistrationComplete(); + authCubit.getTokenByCredentials(email!, password!); } return NavigationDecision.navigate; }, @@ -86,12 +88,8 @@ class _WebViewRegistrationScreenState extends State { @override Widget build(BuildContext context) { return Scaffold( - appBar: AppBar( - toolbarHeight: 0, - ), - body: SafeArea( - child: WebViewWidget(controller: _controller), - ), + appBar: AppBar(toolbarHeight: 0), + body: SafeArea(child: WebViewWidget(controller: _controller)), ); } } diff --git a/lib/features/registration/services/registration_analytics_service.dart b/lib/features/registration/services/registration_analytics_service.dart new file mode 100644 index 00000000..3bfc69d1 --- /dev/null +++ b/lib/features/registration/services/registration_analytics_service.dart @@ -0,0 +1,30 @@ +import 'package:appmetrica_plugin/appmetrica_plugin.dart'; + +class RegistrationAnalyticsService { + static const String _eventPrefix = 'registration_step'; + + /// Reports registration step completion + static void reportRegistrationStep(int stepNumber) { + AppMetrica.reportEvent('$_eventPrefix $stepNumber'); + } + + /// Reports email verification completion + static void reportEmailVerification() { + AppMetrica.reportEvent('registration_email_verify'); + } + + /// Reports phone verification completion + static void reportPhoneVerification() { + AppMetrica.reportEvent('registration_phone_verify'); + } + + /// Reports registration completion + static void reportRegistrationComplete() { + AppMetrica.reportEvent('registration_complete'); + } + + /// Reports registration error + static void reportRegistrationError(String error) { + AppMetrica.reportEvent('registration_error'); + } +}