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; const OtpPage({super.key, required this.phoneNumber}); @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, // جهت متن برای RichText ), 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 AuthVerified) { Navigator.of(context).pushAndRemoveUntil( MaterialPageRoute(builder: (_) => const UserInfoPage()), (route) => false, ); } }, builder: (context, state) { if (state is AuthLoading) { return const Center(child: CircularProgressIndicator()); } return SizedBox( width: double.infinity, child: ElevatedButton( onPressed: _isOtpComplete ? _verifyOtp : null, child: 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)); } } void _resendOtp() { setState(() { _hasError = false; _errorMessage = null; for (var controller in _controllers) { controller.clear(); } _isOtpComplete = false; }); context.read().add(SendOTPEvent(phoneNumber: widget.phoneNumber)); _otpTimer.resetTimer(); } }