forked from eatvio/eatvio_app
Merge pull request 'main' (#661) from main into test-flight
Reviewed-on: eatvio/eatvio_app#661
This commit is contained in:
@@ -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 = (
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
<key>CFBundleSignature</key>
|
||||
<string>????</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>69</string>
|
||||
<string>70</string>
|
||||
<key>ITSAppUsesNonExemptEncryption</key>
|
||||
<false/>
|
||||
<key>LSRequiresIPhoneOS</key>
|
||||
|
||||
@@ -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}'"
|
||||
)
|
||||
|
||||
|
||||
|
||||
@@ -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'] ?? '';
|
||||
|
||||
@@ -47,12 +47,20 @@ class _AuthScreenState extends State<AuthScreen> {
|
||||
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<void> _reconnectWebSocket(BuildContext context) async {
|
||||
try {
|
||||
await sl<WsManager>().clear();
|
||||
|
||||
@@ -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<AddProductModal> createState() => _AddProductModalState();
|
||||
@@ -58,75 +53,62 @@ class _AddProductModalState extends State<AddProductModal> {
|
||||
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<AddProductModal> {
|
||||
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<AddProductModal> {
|
||||
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<AddProductModal> {
|
||||
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<AddProductModal> {
|
||||
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<AddProductModal> {
|
||||
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<AddProductModal> {
|
||||
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),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
@@ -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({
|
||||
|
||||
@@ -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<RegistrationState> {
|
||||
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<void> 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<FirebaseApi>().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()));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<VerifyEmailState> {
|
||||
final ProfileRepository profileRepository;
|
||||
|
||||
VerifyEmailCubit({
|
||||
required this.profileRepository,
|
||||
}) : super(const VerifyEmailState.loading());
|
||||
VerifyEmailCubit({required this.profileRepository}) : super(const VerifyEmailState.loading());
|
||||
|
||||
Future<void> sendEmailCode() async {
|
||||
try {
|
||||
@@ -24,6 +23,7 @@ class VerifyEmailCubit extends Cubit<VerifyEmailState> {
|
||||
emit(const VerifyEmailState.verifyLoading());
|
||||
final result = await profileRepository.tryVerifyEmailByCode(code);
|
||||
if (result == true) {
|
||||
RegistrationAnalyticsService.reportEmailVerification();
|
||||
emit(const VerifyEmailState.verifySuccess());
|
||||
} else {
|
||||
emit(const VerifyEmailState.verifyError());
|
||||
|
||||
@@ -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<RegistrationScreen> {
|
||||
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<RegistrationScreen> {
|
||||
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<RegistrationScreen> {
|
||||
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<RegistrationScreen> {
|
||||
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<RegistrationScreen> {
|
||||
child: TweenAnimationBuilder<double>(
|
||||
duration: const Duration(milliseconds: 350),
|
||||
curve: Curves.easeInOut,
|
||||
tween: Tween<double>(
|
||||
begin: 1 / 15,
|
||||
end: progressValue,
|
||||
),
|
||||
tween: Tween<double>(begin: 1 / 15, end: progressValue),
|
||||
builder: (context, value, _) => LinearProgressIndicator(
|
||||
value: value,
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
@@ -175,6 +140,8 @@ class _RegistrationScreenState extends State<RegistrationScreen> {
|
||||
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<RegistrationScreen> {
|
||||
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<RegistrationScreen> {
|
||||
return true;
|
||||
}
|
||||
case 2:
|
||||
List<String> errors = validateCredentialsFields(
|
||||
state.sheet.email,
|
||||
state.sheet.password,
|
||||
state.sheet.passwordConfirmation,
|
||||
context,
|
||||
);
|
||||
List<String> 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<RegistrationScreen> {
|
||||
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<String> errors = validateNameFields(
|
||||
state.sheet.firstName.toString(),
|
||||
state.sheet.lastName.toString(),
|
||||
context,
|
||||
);
|
||||
List<String> 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<RegistrationScreen> {
|
||||
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<RegistrationScreen> {
|
||||
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<RegistrationScreen> {
|
||||
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<RegistrationScreen> {
|
||||
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<RegistrationScreen> {
|
||||
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<RegistrationScreen> {
|
||||
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<RegistrationScreen> {
|
||||
}
|
||||
}
|
||||
|
||||
List<String> validateCircumferencesFields(
|
||||
String chestCircumference,
|
||||
String waistCircumference,
|
||||
String hipCircumference,
|
||||
BuildContext context,
|
||||
) {
|
||||
List<String> validateCircumferencesFields(String chestCircumference, String waistCircumference, String hipCircumference, BuildContext context) {
|
||||
final s = S.of(context);
|
||||
|
||||
List<String> errors = [];
|
||||
@@ -406,12 +324,7 @@ class _RegistrationScreenState extends State<RegistrationScreen> {
|
||||
return errors;
|
||||
}
|
||||
|
||||
List<String> validateCredentialsFields(
|
||||
String email,
|
||||
String password,
|
||||
String passwordConfirmation,
|
||||
BuildContext context,
|
||||
) {
|
||||
List<String> validateCredentialsFields(String email, String password, String passwordConfirmation, BuildContext context) {
|
||||
final s = S.of(context);
|
||||
|
||||
List<String> errors = [];
|
||||
@@ -435,11 +348,7 @@ class _RegistrationScreenState extends State<RegistrationScreen> {
|
||||
return errors;
|
||||
}
|
||||
|
||||
List<String> validateNameFields(
|
||||
String firstName,
|
||||
String lastName,
|
||||
BuildContext context,
|
||||
) {
|
||||
List<String> validateNameFields(String firstName, String lastName, BuildContext context) {
|
||||
List<String> errors = [];
|
||||
|
||||
if (firstName.isEmpty) {
|
||||
|
||||
@@ -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<VerifyEmailScreen> {
|
||||
@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
|
||||
|
||||
@@ -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<VerifyScreen> createState() => _VerifyScreenState();
|
||||
@@ -25,6 +23,8 @@ class _VerifyScreenState extends State<VerifyScreen> {
|
||||
@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<VerifyScreen> {
|
||||
},
|
||||
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<VerifyScreen> {
|
||||
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<VerifyScreen> {
|
||||
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<VerifyScreen> {
|
||||
child: Stack(
|
||||
children: [
|
||||
WebViewWidget(controller: _controller),
|
||||
if (_isLoading)
|
||||
const Center(
|
||||
child: Loader(),
|
||||
),
|
||||
if (_isLoading) const Center(child: Loader()),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
||||
@@ -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<WebViewRegistrationScreen> {
|
||||
void initState() {
|
||||
super.initState();
|
||||
authCubit = sl<AuthCubit>();
|
||||
// Report webview registration started
|
||||
RegistrationAnalyticsService.reportRegistrationStep(18); // Webview registration step
|
||||
_controller = WebViewController()
|
||||
..setJavaScriptMode(JavaScriptMode.unrestricted)
|
||||
..addJavaScriptChannel(
|
||||
@@ -43,10 +46,9 @@ class _WebViewRegistrationScreenState extends State<WebViewRegistrationScreen> {
|
||||
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<WebViewRegistrationScreen> {
|
||||
@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)),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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');
|
||||
}
|
||||
}
|
||||
@@ -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<void> main() async {
|
||||
WidgetsBinding widgetsBinding = SentryWidgetsFlutterBinding.ensureInitialized();
|
||||
@@ -38,6 +39,9 @@ Future<void> 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;
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user