import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_svg/svg.dart'; import 'package:proxibuy/presentation/auth/bloc/auth_bloc.dart'; import 'package:proxibuy/presentation/pages/user_info_page.dart'; import '../../core/config/app_colors.dart'; import '../../core/gen/assets.gen.dart'; import '../utils/otp_timer_helper.dart'; class OtpPage extends StatefulWidget { final String phoneNumber; final String phone; final String countryCode; const OtpPage({ super.key, required this.phoneNumber, required this.phone, required this.countryCode, }); @override State createState() => _OtpPageState(); } class _OtpPageState extends State { final List _focusNodes = List.generate(5, (_) => FocusNode()); final List _controllers = List.generate( 5, (_) => TextEditingController(), ); late final OtpTimerHelper _otpTimer; bool _hasError = false; String? _errorMessage; bool _isOtpComplete = false; @override void initState() { super.initState(); _otpTimer = OtpTimerHelper()..startTimer(); WidgetsBinding.instance.addPostFrameCallback((_) { FocusScope.of(context).requestFocus(_focusNodes[0]); }); } @override void dispose() { for (final node in _focusNodes) { node.dispose(); } for (final controller in _controllers) { controller.dispose(); } _otpTimer.dispose(); super.dispose(); } @override Widget build(BuildContext context) { final textTheme = Theme.of(context).textTheme; return Directionality( textDirection: TextDirection.rtl, child: Scaffold( body: SafeArea( child: SingleChildScrollView( padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 16), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Center(child: Assets.icons.logo.svg(height: 160)), const SizedBox(height: 40), Text( "کد یکبار مصرف", style: textTheme.headlineMedium?.copyWith( fontWeight: FontWeight.bold, ), ), const SizedBox(height: 12), Text.rich( TextSpan( style: textTheme.titleMedium?.copyWith( color: Colors.grey, height: 1.5, ), children: [ const TextSpan( text: 'کد تایید به شماره ', style: TextStyle(fontSize: 15), ), TextSpan( text: widget.phoneNumber, style: const TextStyle( fontWeight: FontWeight.bold, fontSize: 15, ), ), const TextSpan( text: ' ارسال شد.', style: TextStyle(fontSize: 15), ), ], ), textDirection: TextDirection.rtl, ), SizedBox(height: 15), Row( mainAxisAlignment: MainAxisAlignment.center, children: [ SvgPicture.asset(Assets.icons.vector.path), const SizedBox(width: 4), TextButton( onPressed: () => Navigator.of(context).pop(), child: const Text( "ویرایش شماره همراه", style: TextStyle(color: AppColors.active), ), ), ], ), const SizedBox(height: 15), _buildOtpFields(), const SizedBox(height: 15), if (_errorMessage != null) Padding( padding: const EdgeInsets.only(bottom: 16.0), child: Center( child: Text( _errorMessage!, style: const TextStyle( color: Colors.red, fontWeight: FontWeight.bold, ), ), ), ) else const SizedBox(height: 32), BlocConsumer( listener: (context, state) { if (state is AuthFailure) { setState(() { _hasError = true; _errorMessage = state.message; }); } if (state is AuthNeedsInfo) { Navigator.of(context).pushAndRemoveUntil( MaterialPageRoute(builder: (_) => const UserInfoPage()), (route) => false, ); } }, builder: (context, state) { bool isLoading = state is AuthLoading; if (state is AuthLoading) { return const Center(child: CircularProgressIndicator()); } return SizedBox( width: double.infinity, height: 60, child: ElevatedButton( onPressed: (_isOtpComplete && !isLoading) ? _verifyOtp : null, child: isLoading ? const CircularProgressIndicator( color: Colors.white, strokeWidth: 3, ) : const Text("ورود"), ), ); }, ), const SizedBox(height: 32), Row( mainAxisAlignment: MainAxisAlignment.center, children: [ SvgPicture.asset( Assets.icons.clock.path, colorFilter: const ColorFilter.mode( Colors.grey, BlendMode.srcIn, ), ), const SizedBox(width: 8), ValueListenableBuilder( valueListenable: _otpTimer.canResend, builder: (context, canResend, child) { return canResend ? TextButton( onPressed: _resendOtp, child: const Text( "ارسال مجدد کد", style: TextStyle(color: AppColors.active), ), ) : ValueListenableBuilder( valueListenable: _otpTimer.remainingSeconds, builder: (context, seconds, child) => Text( "${_otpTimer.formatTime()} تا دریافت مجدد", style: const TextStyle(color: Colors.grey), ), ); }, ), ], ), ], ), ), ), ), ); } Widget _buildOtpFields() { return Directionality( textDirection: TextDirection.ltr, child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: List.generate(5, (index) { return SizedBox( width: 60, height: 60, child: TextField( controller: _controllers[index], focusNode: _focusNodes[index], keyboardType: TextInputType.number, textAlign: TextAlign.center, maxLength: 1, style: TextStyle( fontSize: 22, fontWeight: FontWeight.bold, color: _hasError ? Colors.red : Colors.black, ), decoration: InputDecoration( counterText: '', enabledBorder: OutlineInputBorder( borderRadius: BorderRadius.circular(12), borderSide: BorderSide( color: _hasError ? Colors.red : (Theme.of(context) .inputDecorationTheme .enabledBorder ?.borderSide .color ?? Colors.grey), ), ), focusedBorder: OutlineInputBorder( borderRadius: BorderRadius.circular(12), borderSide: BorderSide( color: _hasError ? Colors.red : AppColors.active, width: 2, ), ), ), onChanged: (value) { if (_hasError) { setState(() { _hasError = false; _errorMessage = null; }); } if (value.length == 1 && index < 4) { FocusScope.of(context).requestFocus(_focusNodes[index + 1]); } if (value.isEmpty && index > 0) { FocusScope.of(context).requestFocus(_focusNodes[index - 1]); } final otpCode = _controllers.map((c) => c.text).join(); setState(() { _isOtpComplete = otpCode.length == 5; }); }, ), ); }), ), ); } void _verifyOtp() { final otpCode = _controllers.map((c) => c.text).join(); if (otpCode.length == 5) { context.read().add( VerifyOTPEvent( otp: otpCode, phoneNumber: widget.phone, countryCode: widget.countryCode, ), ); } } void _resendOtp() { for (var controller in _controllers) { controller.clear(); } FocusScope.of(context).requestFocus(_focusNodes[0]); setState(() => _isOtpComplete = false); context.read().add( SendOTPEvent(phoneNumber: widget.phone, countryCode: widget.countryCode), ); _otpTimer.resetTimer(); } }