diff --git a/ios/Runner.xcodeproj/project.pbxproj b/ios/Runner.xcodeproj/project.pbxproj
index d6e7401a..4447f176 100644
--- a/ios/Runner.xcodeproj/project.pbxproj
+++ b/ios/Runner.xcodeproj/project.pbxproj
@@ -396,7 +396,7 @@
CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements;
CODE_SIGN_IDENTITY = "Apple Distribution: ITVIO, OOO (VL68C36HYS)";
CODE_SIGN_STYLE = Manual;
- CURRENT_PROJECT_VERSION = 69;
+ CURRENT_PROJECT_VERSION = 70;
DEVELOPMENT_TEAM = VL68C36HYS;
ENABLE_BITCODE = NO;
FRAMEWORK_SEARCH_PATHS = (
@@ -546,7 +546,7 @@
CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements;
CODE_SIGN_IDENTITY = "Apple Distribution: ITVIO, OOO (VL68C36HYS)";
CODE_SIGN_STYLE = Manual;
- CURRENT_PROJECT_VERSION = 69;
+ CURRENT_PROJECT_VERSION = 70;
DEVELOPMENT_TEAM = VL68C36HYS;
ENABLE_BITCODE = NO;
FRAMEWORK_SEARCH_PATHS = (
@@ -584,7 +584,7 @@
CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements;
CODE_SIGN_IDENTITY = "Apple Distribution: ITVIO, OOO (VL68C36HYS)";
CODE_SIGN_STYLE = Manual;
- CURRENT_PROJECT_VERSION = 69;
+ CURRENT_PROJECT_VERSION = 70;
DEVELOPMENT_TEAM = VL68C36HYS;
ENABLE_BITCODE = NO;
FRAMEWORK_SEARCH_PATHS = (
diff --git a/ios/Runner/Info.plist b/ios/Runner/Info.plist
index 7e814e8a..f0baee2b 100644
--- a/ios/Runner/Info.plist
+++ b/ios/Runner/Info.plist
@@ -21,7 +21,7 @@
CFBundleSignature
????
CFBundleVersion
- 69
+ 70
ITSAppUsesNonExemptEncryption
LSRequiresIPhoneOS
diff --git a/ios/fastlane/Fastfile b/ios/fastlane/Fastfile
index ebd745be..7ccc8b7a 100644
--- a/ios/fastlane/Fastfile
+++ b/ios/fastlane/Fastfile
@@ -54,7 +54,6 @@ platform :ios do
scheme: "Runner",
export_method: "app-store",
codesigning_identity: "Apple Distribution",
- xcargs: "--dart-define=HIDE_PLANS=true --dart-define=BUILD_DATE='#{build_date}'"
)
diff --git a/lib/core/env_config.dart b/lib/core/env_config.dart
index b363ad70..a3404aeb 100644
--- a/lib/core/env_config.dart
+++ b/lib/core/env_config.dart
@@ -7,6 +7,9 @@ class EnvConfig {
// Sentry Configuration
static String get sentryDsn => dotenv.env['SENTRY_DSN'] ?? '';
+ // AppMetrica Configuration
+ static String get appMetricaApiKey => dotenv.env['APPMETRICA_KEY'] ?? '';
+
// Firebase Configuration
static String get firebaseProjectId => dotenv.env['FIREBASE_PROJECT_ID'] ?? '';
static String get firebaseDatabaseUrl => dotenv.env['FIREBASE_DATABASE_URL'] ?? '';
diff --git a/lib/features/auth/presentation/pages/auth_screen.dart b/lib/features/auth/presentation/pages/auth_screen.dart
index 20b0e26c..73162bd2 100644
--- a/lib/features/auth/presentation/pages/auth_screen.dart
+++ b/lib/features/auth/presentation/pages/auth_screen.dart
@@ -47,12 +47,20 @@ class _AuthScreenState extends State {
super.initState();
var keyboardVisibilityController = KeyboardVisibilityController();
keyboardSubscription = keyboardVisibilityController.onChange.listen((bool visible) {
- setState(() {
- isFocused = visible;
- });
+ if (mounted) {
+ setState(() {
+ isFocused = visible;
+ });
+ }
});
}
+ @override
+ void dispose() {
+ keyboardSubscription.cancel();
+ super.dispose();
+ }
+
Future _reconnectWebSocket(BuildContext context) async {
try {
await sl().clear();
diff --git a/lib/features/diary/presentation/widgets/add_product_modal.dart b/lib/features/diary/presentation/widgets/add_product_modal.dart
index b55a979f..9b6c2536 100644
--- a/lib/features/diary/presentation/widgets/add_product_modal.dart
+++ b/lib/features/diary/presentation/widgets/add_product_modal.dart
@@ -14,12 +14,7 @@ class AddProductModal extends StatefulWidget {
final DiaryStateLoaded state;
final DiaryCubit diaryCubit;
- const AddProductModal({
- super.key,
- this.portion,
- required this.state,
- required this.diaryCubit,
- });
+ const AddProductModal({super.key, this.portion, required this.state, required this.diaryCubit});
@override
State createState() => _AddProductModalState();
@@ -58,75 +53,62 @@ class _AddProductModalState extends State {
return StatefulBuilder(
builder: (BuildContext context, void Function(void Function()) setState) {
return Padding(
- padding: const EdgeInsets.all(16) +
- EdgeInsets.only(
- bottom: MediaQuery.of(context).viewInsets.bottom,
- ),
+ padding: const EdgeInsets.all(16) + EdgeInsets.only(bottom: MediaQuery.of(context).viewInsets.bottom),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
children: [
const Text(
'Добавление блюда',
- style: TextStyle(
- fontSize: 20,
- fontWeight: FontWeight.w600,
- color: Color(0xFF323F4B),
- ),
+ style: TextStyle(fontSize: 20, fontWeight: FontWeight.w600, color: Color(0xFF323F4B)),
),
const SizedBox(height: 16),
Row(
mainAxisSize: MainAxisSize.min,
children: [
SizedBox(
- width: MediaQuery.of(context).size.width * 0.8 - 32,
+ width: MediaQuery.of(context).size.width * 0.76 - 32,
child: CustomTextFormField(
controller: productNameController,
autofocus: true,
hintText: 'Введите название продукта/блюда',
onChanged: (query) {
- EasyDebounce.debounce(
- 'glossary-search',
- const Duration(milliseconds: 250),
- () async {
- selectedGlossary = null;
- if (query.isEmpty) {
- glossariesList = [];
- setState(() {});
- return;
- }
- glossariesList = await widget.diaryCubit.getGlossaryListByQuery(query);
+ EasyDebounce.debounce('glossary-search', const Duration(milliseconds: 250), () async {
+ selectedGlossary = null;
+ if (query.isEmpty) {
+ glossariesList = [];
setState(() {});
- },
- );
+ return;
+ }
+ glossariesList = await widget.diaryCubit.getGlossaryListByQuery(query);
+ setState(() {});
+ });
},
),
),
const SizedBox(width: 4),
SizedBox(
- width: MediaQuery.of(context).size.width * 0.2 - 4,
+ width: MediaQuery.of(context).size.width * 0.24 - 4,
child: CustomTextFormField(
controller: quantityController,
keyboardType: TextInputType.number,
hintText: selectedGlossary == null
? '100'
: selectedGlossary!.grInPortion == 0
- ? '100'
- : '1',
+ ? '100'
+ : '1',
suffixText: selectedGlossary == null
? 'гр'
: selectedGlossary?.grInPortion == 0
- ? 'гр'
- : 'шт',
+ ? 'гр'
+ : 'шт',
),
),
],
),
glossariesList.isEmpty ? const SizedBox(height: 16) : const SizedBox.shrink(),
Container(
- constraints: const BoxConstraints(
- maxHeight: 256,
- ),
+ constraints: const BoxConstraints(maxHeight: 256),
child: ListView.separated(
physics: const BouncingScrollPhysics(),
shrinkWrap: true,
@@ -141,28 +123,12 @@ class _AddProductModalState extends State {
setState(() {});
FocusManager.instance.primaryFocus?.nextFocus();
},
- shape: RoundedRectangleBorder(
- borderRadius: BorderRadius.circular(20),
- ),
- title: Text(
- glossariesList[i].name,
- style: const TextStyle(
- height: 1,
- ),
- ),
+ shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(20)),
+ title: Text(glossariesList[i].name, style: const TextStyle(height: 1)),
subtitle: glossariesList[i].grInPortion == 0
? null
- : Text(
- '${glossariesList[i].grInPortion} г',
- style: TextStyle(
- height: 1,
- color: Colors.grey.shade600,
- ),
- ),
- trailing: Icon(
- Icons.add_rounded,
- color: Colors.grey.shade400,
- ),
+ : Text('${glossariesList[i].grInPortion} г', style: TextStyle(height: 1, color: Colors.grey.shade600)),
+ trailing: Icon(Icons.add_rounded, color: Colors.grey.shade400),
contentPadding: const EdgeInsets.only(left: 12),
);
},
@@ -188,14 +154,8 @@ class _AddProductModalState extends State {
child: Ink(
width: 52,
height: 52,
- decoration: BoxDecoration(
- borderRadius: BorderRadius.circular(20),
- color: const Color(0xFFF6F6F6),
- ),
- child: const Icon(
- Icons.delete_outline_rounded,
- color: Colors.red,
- ),
+ decoration: BoxDecoration(borderRadius: BorderRadius.circular(20), color: const Color(0xFFF6F6F6)),
+ child: const Icon(Icons.delete_outline_rounded, color: Colors.red),
),
),
if (widget.portion != null) const SizedBox(width: 8),
@@ -209,8 +169,8 @@ class _AddProductModalState extends State {
portion: selectedGlossary == null
? 'gr'
: selectedGlossary!.grInPortion == 0
- ? 'gr'
- : 'pcs',
+ ? 'gr'
+ : 'pcs',
quantity: int.parse(quantityController.text),
productNutritionalValueId: selectedGlossary?.id,
);
@@ -223,8 +183,8 @@ class _AddProductModalState extends State {
portion: selectedGlossary == null
? 'gr'
: selectedGlossary!.grInPortion == 0
- ? 'gr'
- : 'pcs',
+ ? 'gr'
+ : 'pcs',
quantity: int.parse(quantityController.text),
productNutritionalValueId: selectedGlossary?.id,
portionState: widget.portion!.portionState,
@@ -237,13 +197,8 @@ class _AddProductModalState extends State {
useRootNavigator: true,
context: context,
builder: (context) => Padding(
- padding: EdgeInsets.only(
- bottom: MediaQuery.of(context).viewInsets.bottom,
- ),
- child: GlossaryModerationRequestModalWidget(
- productName: productNameController.text,
- diaryCubit: widget.diaryCubit,
- ),
+ padding: EdgeInsets.only(bottom: MediaQuery.of(context).viewInsets.bottom),
+ child: GlossaryModerationRequestModalWidget(productName: productNameController.text, diaryCubit: widget.diaryCubit),
),
);
}
@@ -252,18 +207,11 @@ class _AddProductModalState extends State {
child: Ink(
width: MediaQuery.of(context).size.width - (widget.portion == null ? 32 : 92),
height: 52,
- decoration: BoxDecoration(
- borderRadius: BorderRadius.circular(20),
- color: appTheme.blueA400,
- ),
+ decoration: BoxDecoration(borderRadius: BorderRadius.circular(20), color: appTheme.blueA400),
child: Center(
child: Text(
widget.portion == null ? 'Добавить' : 'Сохранить',
- style: const TextStyle(
- fontSize: 18,
- fontWeight: FontWeight.w600,
- color: Colors.white,
- ),
+ style: const TextStyle(fontSize: 18, fontWeight: FontWeight.w600, color: Colors.white),
),
),
),
diff --git a/lib/features/feed/presentation/widgets/diets_widget.dart b/lib/features/feed/presentation/widgets/diets_widget.dart
index 34964d1a..d91cdd74 100644
--- a/lib/features/feed/presentation/widgets/diets_widget.dart
+++ b/lib/features/feed/presentation/widgets/diets_widget.dart
@@ -17,7 +17,7 @@ import 'package:flutter_svg/flutter_svg.dart';
import 'package:go_router/go_router.dart';
import 'package:share_plus/share_plus.dart';
-class DietsWidget extends StatefulWidget {
+class DietsWidget extends StatefulWidget {
final Diet diet;
final int index;
const DietsWidget({
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');
+ }
+}
diff --git a/lib/main.dart b/lib/main.dart
index 6b44348c..468de90f 100644
--- a/lib/main.dart
+++ b/lib/main.dart
@@ -23,6 +23,7 @@ import 'package:keyboard_dismisser/keyboard_dismisser.dart';
import 'package:sentry_flutter/sentry_flutter.dart';
import 'package:flutter_dotenv/flutter_dotenv.dart';
import 'package:eatvio_app/core/env_config.dart';
+import 'package:appmetrica_plugin/appmetrica_plugin.dart';
Future main() async {
WidgetsBinding widgetsBinding = SentryWidgetsFlutterBinding.ensureInitialized();
@@ -38,6 +39,9 @@ Future main() async {
io.HttpClient.enableTimelineLogging = true;
}
+ AppMetrica.activate(AppMetricaConfig(EnvConfig.appMetricaApiKey));
+ AppMetrica.reportEvent('Application started');
+
await SentryFlutter.init((options) {
options.dsn = EnvConfig.sentryDsn;
options.tracesSampleRate = 1.0;
diff --git a/pubspec.yaml b/pubspec.yaml
index 3b33303b..1505f232 100644
--- a/pubspec.yaml
+++ b/pubspec.yaml
@@ -1,6 +1,6 @@
name: eatvio_app
description: A new Flutter project.
-version: 1.2.10+70
+version: 1.2.11+71
publish_to: 'none'
environment:
@@ -73,6 +73,7 @@ dependencies:
qr_code_scanner_plus: ^2.0.10+1
flutter_dotenv: ^5.2.1
uuid: ^4.5.1
+ appmetrica_plugin: ^3.3.0
dev_dependencies:
build_runner: ^2.7.0