import 'dart:async'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:formz/formz.dart'; import 'package:go_router/go_router.dart'; import '../../../../core/router/app_routes.dart'; import '../../../../core/theme/design_tokens.dart'; import '../../../../shared/widgets/app_button.dart'; import '../../../../shared/widgets/banner/app_banner.dart'; import '../../../../shared/widgets/fixed_length_code_input.dart'; import '../../../../shared/widgets/link_button.dart'; import '../../../../shared/widgets/toast/toast.dart'; import '../../../../shared/widgets/toast/toast_type.dart'; import '../../presentation/bloc/auth_bloc.dart'; import '../../presentation/bloc/auth_event.dart'; import '../../presentation/cubits/register_cubit.dart'; import '../widgets/auth_page_scaffold.dart'; class RegisterVerificationScreen extends StatelessWidget { const RegisterVerificationScreen({super.key, this.cubit}); final RegisterCubit? cubit; @override Widget build(BuildContext context) { final registerCubit = cubit ?? (GoRouterState.of(context).extra as RegisterCubit?); if (registerCubit == null) { return const Scaffold( body: Center(child: Text('RegisterCubit not found')), ); } return BlocProvider.value( value: registerCubit, child: const RegisterVerificationView(), ); } } class RegisterVerificationView extends StatefulWidget { const RegisterVerificationView({super.key}); @override State createState() => _RegisterVerificationViewState(); } class _RegisterVerificationViewState extends State { final _codeController = TextEditingController(); Timer? _countdownTimer; int _countdown = 0; bool _firstSendCompleted = false; @override void dispose() { _countdownTimer?.cancel(); _codeController.dispose(); super.dispose(); } void _startCountdown() { setState(() { _countdown = 60; }); _countdownTimer?.cancel(); _countdownTimer = Timer.periodic(const Duration(seconds: 1), (timer) { if (_countdown > 0) { setState(() { _countdown--; }); } else { timer.cancel(); } }); } Future _handleComplete() async { final cubit = context.read(); cubit.verificationCodeChanged(_codeController.text); if (!cubit.state.isStep2Valid) { Toast.show( context, _codeController.text.isEmpty ? '请输入验证码' : '验证码必须是 6 位数字', type: ToastType.warning, ); return; } final response = await cubit.submitStep2(); if (response != null && mounted) { context.read().add(AuthLoggedIn(user: response.user)); context.go(AppRoutes.homeMain); } } Future _handleResendCode() async { final cubit = context.read(); final success = await cubit.resendCode(); if (success && mounted) { _startCountdown(); Toast.show(context, '验证码已发送', type: ToastType.success); } } @override Widget build(BuildContext context) { return AuthPageScaffold( mainContent: Padding( padding: const EdgeInsets.symmetric(horizontal: AppSpacing.sm), child: ConstrainedBox( constraints: const BoxConstraints(maxWidth: 388), child: BlocConsumer( listener: (context, state) { if (!mounted) { return; } if (state.status == FormzSubmissionStatus.failure && state.errorMessage != null) { Toast.show(context, state.errorMessage!, type: ToastType.error); if (!_firstSendCompleted) { _firstSendCompleted = true; setState(() { _countdown = 0; }); } } if (state.status == FormzSubmissionStatus.success && !_firstSendCompleted) { _firstSendCompleted = true; _startCountdown(); Toast.show(context, '验证码已发送', type: ToastType.info); } }, builder: (context, state) { final isSubmitting = state.status == FormzSubmissionStatus.inProgress; final canResend = _countdown == 0 && !isSubmitting; return Column( mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.center, children: [ const AuthHeroHeader(showBrand: true), SizedBox(height: AppSpacing.xl), AuthSurfaceCard( child: Column( crossAxisAlignment: CrossAxisAlignment.stretch, children: [ const Text( '验证邮箱', style: TextStyle( fontSize: 20, fontWeight: FontWeight.w700, color: AppColors.slate900, ), ), SizedBox(height: AppSpacing.lg), AuthSection( title: '验证码', child: Column( crossAxisAlignment: CrossAxisAlignment.stretch, children: [ FixedLengthCodeInput( controller: _codeController, length: 6, semanticLabel: '邮箱验证码输入框', keyboardType: TextInputType.number, allowedCharacters: const { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', }, onChanged: (value) { context .read() .verificationCodeChanged(value); }, ), SizedBox(height: AppSpacing.md), AppBanner( title: '验证码', message: canResend ? '6 位数字验证码。' : '$_countdown 秒后可重新发送。', type: ToastType.info, ), ], ), ), SizedBox(height: AppSpacing.lg), AppButton( text: '完成注册', onPressed: isSubmitting ? null : _handleComplete, isLoading: isSubmitting, ), SizedBox(height: AppSpacing.sm), Center( child: LinkButton( text: canResend ? '重新发送验证码' : '$_countdown 秒后重发', onTap: canResend ? _handleResendCode : null, enabled: canResend, ), ), ], ), ), ], ); }, ), ), ), footer: Row( mainAxisAlignment: MainAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.center, children: [ const Text( '已有账号?', style: TextStyle(fontSize: 14, color: AppColors.authLinkMuted), ), LinkButton(text: '去登录', onTap: () => context.go('/')), ], ), ); } }