Files
eatvio_app/lib/features/registration/presentation/pages/verify_email_screen.dart

219 lines
9.2 KiB
Dart
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import 'dart:async';
import 'package:eatvio_app/core/locator_service.dart';
import 'package:eatvio_app/core/theme/custom_text_style.dart';
import 'package:eatvio_app/core/theme/theme_helper.dart';
import 'package:eatvio_app/core/utils/toasts.dart';
import 'package:eatvio_app/core/widgets/custom_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/presentation/widgets/change_email_dialog.dart';
import 'package:eatvio_app/features/registration/presentation/widgets/code_not_received_dialog.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';
class VerifyEmailScreen extends StatefulWidget {
const VerifyEmailScreen({super.key});
@override
State<VerifyEmailScreen> createState() => _VerifyEmailScreenState();
}
class _VerifyEmailScreenState extends State<VerifyEmailScreen> {
final List<FocusNode> _focusNodes = List.generate(4, (_) => FocusNode());
final List<TextEditingController> _controllers = List.generate(4, (_) => TextEditingController());
String email = '';
late final GoRouter goRouter;
late Timer _timer;
int _secondsRemaining = 60;
bool _canSendAgain = false;
@override
void initState() {
super.initState();
goRouter = GoRouter.of(context);
_startTimer();
// Report email verification screen opened
RegistrationAnalyticsService.reportRegistrationStep(16); // Email verification step
}
@override
void dispose() {
for (var controller in _controllers) {
controller.dispose();
}
for (var node in _focusNodes) {
node.dispose();
}
super.dispose();
}
void _startTimer() {
setState(() {
_canSendAgain = false;
_secondsRemaining = 60;
});
_timer = Timer.periodic(const Duration(seconds: 1), (timer) {
setState(() {
if (_secondsRemaining > 0) {
_secondsRemaining--;
} else {
_canSendAgain = true;
_timer.cancel();
}
});
});
}
void _checkAndSubmitCode(BuildContext context) {
final code = _controllers.map((c) => c.text).join();
if (code.length == 4) {
context.read<VerifyEmailCubit>().tryVerify(code);
}
}
@override
Widget build(BuildContext context) {
return BlocProvider<VerifyEmailCubit>(
create: (_) => sl<VerifyEmailCubit>()..sendEmailCode(),
child: Builder(
builder: (context) {
return BlocConsumer<VerifyEmailCubit, VerifyEmailState>(
listener: (context, state) {
if (state is VerifyEmailStateLoaded) {
setState(() {
email = state.email;
});
_startTimer();
} else if (state is VerifyEmailStateVerifySuccess) {
showSuccessToast('Почта успешно подтверждена');
goRouter.pushNamed('feedScreen');
} else if (state is VerifyEmailStateVerifyError) {
showErrorToast("Неверный код. Попробуйте снова.");
for (var controller in _controllers) {
controller.clear();
}
FocusScope.of(context).requestFocus(_focusNodes[0]);
} else if (state is VerifyEmailStateChangeEmailSuccess) {
showSuccessToast('Код подтверждения отправлен на новый email');
// Диалог закроется автоматически в ChangeEmailDialog при успешной смене
} else if (state is VerifyEmailStateChangeEmailError) {
showErrorToast('Ошибка при смене email. Попробуйте снова.');
} else if (state is VerifyEmailStateSkipped) {
goRouter.pushNamed('feedScreen');
}
},
builder: (context, state) {
return Scaffold(
appBar: AppBar(title: const Text("Подтвердите почту"), ),
body: Padding(
padding: const EdgeInsets.all(24.0),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text("Код отправлен на почту\n$email", textAlign: TextAlign.center),
const SizedBox(height: 20),
Row(mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: List.generate(4, (index) => _buildCodeField(context, index))),
const SizedBox(height: 16),
GestureDetector(
onTap: () {
final verifyEmailCubit = context.read<VerifyEmailCubit>();
showDialog(context: context, builder: (context) => CodeNotReceivedDialog(
currentEmail: email,
verifyEmailCubit: verifyEmailCubit,
));
},
child: Text(
'Не приходит код?',
style: TextStyles.textSMedium.copyWith(color: appTheme.blueA400, decoration: TextDecoration.underline),
),
),
const SizedBox(height: 24),
CustomOutlinedButton(
text: 'Запросить код повторно',
height: 52,
isDisabled: !_canSendAgain,
buttonTextStyle: TextStyles.textMSemibold.copyWith(color: appTheme.whiteA700),
buttonStyle: ButtonStyle(
side: const WidgetStatePropertyAll(BorderSide.none),
backgroundColor: WidgetStatePropertyAll(_canSendAgain ? appTheme.blueA400 : appTheme.gray),
),
onPressed: () async {
await context.read<VerifyEmailCubit>().sendEmailCode();
_startTimer();
},
),
const SizedBox(height: 8),
if (!_canSendAgain)
Text("Зпросить код повторно можно через $_secondsRemaining с.", style: TextStyles.textSMedium.copyWith(color: appTheme.gray)),
const SizedBox(height: 16),
CustomOutlinedButton(
text: 'Сменить email',
height: 52,
buttonTextStyle: TextStyles.textMSemibold.copyWith(color: appTheme.blueA400),
buttonStyle: ButtonStyle(
side: WidgetStatePropertyAll(BorderSide(color: appTheme.blueA400)),
backgroundColor: const WidgetStatePropertyAll(Colors.transparent),
),
onPressed: () {
final verifyEmailCubit = context.read<VerifyEmailCubit>();
showDialog(
context: context,
builder: (context) => ChangeEmailDialog(currentEmail: email, verifyEmailCubit: verifyEmailCubit),
);
},
),
const SizedBox(height: 16),
CustomOutlinedButton(
text: 'Позже',
height: 52,
buttonTextStyle: TextStyles.textMSemibold.copyWith(color: appTheme.black900),
buttonStyle: ButtonStyle(
side: const WidgetStatePropertyAll(BorderSide.none),
backgroundColor: WidgetStatePropertyAll(appTheme.gray.withValues(alpha: 0.1)),
),
onPressed: () {
context.read<VerifyEmailCubit>().skipVerification();
},
),
],
),
),
);
},
);
},
),
);
}
Widget _buildCodeField(BuildContext context, int index) {
return SizedBox(
width: 50,
child: TextField(
controller: _controllers[index],
focusNode: _focusNodes[index],
keyboardType: TextInputType.number,
textAlign: TextAlign.center,
maxLength: 1,
style: const TextStyle(fontSize: 24),
decoration: InputDecoration(
counterText: "",
border: OutlineInputBorder(borderRadius: BorderRadius.circular(8.0)),
),
onChanged: (value) {
if (value.isNotEmpty && index < 3) {
FocusScope.of(context).requestFocus(_focusNodes[index + 1]);
}
if (value.isEmpty && index > 0) {
FocusScope.of(context).requestFocus(_focusNodes[index - 1]);
}
_checkAndSubmitCode(context);
},
),
);
}
}