import 'dart:async'; import 'dart:developer'; import 'package:wanisdriver/Provider/sms_provider.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:provider/provider.dart'; import '../Provider/SignUp_provider.dart'; import '../config/AppSnackbar.dart'; import '../utils/colors.dart'; import '../widgets/custom_button.dart'; import '../widgets/custom_text.dart'; import 'Password/ChangePasswordLastStepScreen.dart'; import 'SignupAddPassword.dart'; class OtpVerificationScreen extends StatefulWidget { final bool? isForgetPassword; final String? phoneNumber; final bool? isSimpleVerification; const OtpVerificationScreen( {super.key, this.isForgetPassword, this.phoneNumber, this.isSimpleVerification}); @override State createState() => _OtpVerificationScreenState(); } class _OtpVerificationScreenState extends State with TickerProviderStateMixin { final List _controllers = List.generate(4, (index) => TextEditingController()); final List _focusNodes = List.generate(4, (index) => FocusNode()); late AnimationController _animationController; late Animation _fadeAnimation; late Animation _slideAnimation; Timer? _timer; int _remainingTime = 60; bool _canResend = false; String _otpCode = ''; @override void initState() { super.initState(); _startTimer(); _setupAnimations(); } void _setupAnimations() { _animationController = AnimationController( duration: const Duration(milliseconds: 800), vsync: this, ); _fadeAnimation = Tween( begin: 0.0, end: 1.0, ).animate(CurvedAnimation( parent: _animationController, curve: Curves.easeInOut, )); _slideAnimation = Tween( begin: const Offset(0.0, 0.5), end: Offset.zero, ).animate(CurvedAnimation( parent: _animationController, curve: Curves.easeOutCubic, )); _animationController.forward(); } void _startTimer() { _canResend = false; _remainingTime = 60; _timer = Timer.periodic(const Duration(seconds: 1), (timer) { if (_remainingTime > 0) { setState(() { _remainingTime--; }); } else { setState(() { _canResend = true; }); timer.cancel(); } }); } void _onOtpChanged(int index, String value) { if (value.isNotEmpty && index < 3) { _focusNodes[index + 1].requestFocus(); } else if (value.isEmpty && index > 0) { _focusNodes[index - 1].requestFocus(); } // Build OTP string _otpCode = _controllers.map((controller) => controller.text).join(); // Auto-verify if all fields are filled if (_otpCode.length == 4) { _verifyOtp(); } } void _verifyOtp() async { log("otp code is $_otpCode"); final provider = Provider.of(context, listen: false); final providersms = Provider.of(context, listen: false); if (_otpCode.length != 4) { AppSnackbar.showError("Error", "Please enter the complete OTP code."); return; } try { if (widget.isForgetPassword != null && widget.isForgetPassword!) { bool res = await provider.validateResetPasswordOTP( widget.phoneNumber!, _otpCode, context); if (res) { Navigator.push( context, MaterialPageRoute( builder: (context) => ChangePasswordLastStepScreen(code: _otpCode,))); } } else { bool result = await providersms.verify_sms(widget.phoneNumber!,_otpCode); if(!result){ throw Exception('OTP verification failed'); } //TODO add signup driver call and move to Navigator.push( context, MaterialPageRoute( builder: (context) => SignUpScreenAddPassword())); } } catch (e) { AppSnackbar.showError("Error", "Invalid OTP code. Please try again."); _clearOtp(); } } Future _resendOtp() async { final providersms = Provider.of(context, listen: false); // Add your resend OTP logic here bool result = await providersms.send_sms(widget.phoneNumber!); if(result){ AppSnackbar.showInfo("Info", "OTP code has been resent to your phone."); }else{ AppSnackbar.showInfo("Info", "Session is expired please signup again."); } _startTimer(); _clearOtp(); } void _clearOtp() { for (var controller in _controllers) { controller.clear(); } _focusNodes[0].requestFocus(); _otpCode = ''; } String get _formattedPhoneNumber { final provider = Provider.of(context); final phone = provider.phone ?? ''; return phone; if (phone.length >= 4) { return '${phone.substring(0, phone.length - 4).replaceAll(RegExp(r'.'), '*')}${phone.substring(phone.length - 4)}'; } return phone; } @override void dispose() { _timer?.cancel(); _animationController.dispose(); for (var controller in _controllers) { controller.dispose(); } for (var node in _focusNodes) { node.dispose(); } super.dispose(); } @override Widget build(BuildContext context) { final isDark = Theme.of(context).brightness == Brightness.dark; final provider = context.watch(); final smsprovider = context.watch(); return Scaffold( backgroundColor: isDark ? AppColors.blackBackgroundColor : AppColors.whiteBackgroundColor, appBar: AppBar( backgroundColor: Theme.of(context).brightness == Brightness.dark ? AppColors.blackBackgroundColor : AppColors.whiteBackgroundColor, elevation: 0, leading: Padding( padding: const EdgeInsets.all(8.0), child: Container( decoration: BoxDecoration( shape: BoxShape.circle, color: Theme.of(context).brightness == Brightness.dark ? Colors.grey.shade800 : Colors.grey.shade200, ), child: IconButton( icon: Icon( Icons.arrow_back, color: Theme.of(context).brightness == Brightness.dark ? AppColors.white : AppColors.black, size: 20, ), onPressed: () => Navigator.pop(context), ), ), ), // title: Txt( // text: "Verify Phone Number", // style: TextStyle( // color: Theme.of(context).brightness == Brightness.dark // ? AppColors.white // : AppColors.black, // fontSize: 18, // fontWeight: FontWeight.w600, // fontFamily: 'Poppins', // ), // ), centerTitle: true, ), body: FadeTransition( opacity: _fadeAnimation, child: SlideTransition( position: _slideAnimation, child: SingleChildScrollView( padding: const EdgeInsets.all(15.0), child: Column( crossAxisAlignment: CrossAxisAlignment.center, children: [ // const SizedBox(height: 40), // Illustration or Icon // Container( // width: 120, // height: 120, // decoration: BoxDecoration( // shape: BoxShape.circle, // color: isDark // ? AppColors.darkPrimaryColor.withOpacity(0.1) // : AppColors.primaryColor.withOpacity(0.1), // border: Border.all( // color: isDark // ? AppColors.darkPrimaryColor.withOpacity(0.3) // : AppColors.primaryColor.withOpacity(0.3), // width: 2, // ), // ), // child: const Icon( // Icons.sms_outlined, // size: 60, // color: AppColors.primaryColor, // ), // ), // const SizedBox(height: 0), // Title and Description Txt( text: 'Phone Number Verification', style: TextStyle( fontSize: 28, fontWeight: FontWeight.bold, fontFamily: 'Poppins', color: isDark ? AppColors.whiteBackgroundColor : AppColors.blackBackgroundColor, ), ), const SizedBox(height: 16), Txt( text: 'We have sent a 4-digit verification code to ($_formattedPhoneNumber)', style: TextStyle( fontSize: 16, fontFamily: 'Poppins', fontWeight: FontWeight.w600, color: isDark ? AppColors.darkSecondaryText : const Color(0xFF666666), ), ), // const SizedBox(height: 8), // Txt( // text: '', // style: TextStyle( // fontSize: 16, // fontWeight: FontWeight.w600, // fontFamily: 'Poppins', // color: isDark // ? AppColors.darkSecondaryText // : const Color(0xFF666666), // ), // ), const SizedBox(height: 25), // OTP Input Fields Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: List.generate(5, (index) { if (index == 2) { // Changed from index == 1 to index == 2 return Txt( text: '—', style: TextStyle( fontSize: 24, fontWeight: FontWeight.bold, color: isDark ? AppColors.darkSecondaryText : Colors.grey.shade400, ), ); } // Adjust index for controllers/focusNodes int actualIndex = index > 2 ? index - 1 : index; return _buildOtpField(actualIndex); }), ), const SizedBox(height: 10), // Timer and Resend if (!_canResend) Txt( text: 'Resend code in ${_remainingTime}s', style: TextStyle( fontSize: 16, fontFamily: 'Poppins', color: isDark ? AppColors.darkSecondaryText : const Color(0xFF666666), ), ) else Row( mainAxisAlignment: MainAxisAlignment.start, children: [ Txt( text: "Didn't receive the code? ", style: TextStyle( fontSize: 16, fontFamily: 'Poppins', color: isDark ? AppColors.darkSecondaryText : const Color(0xFF666666), ), ), GestureDetector( onTap: _resendOtp, child: const Txt( text: 'Resend', style: TextStyle( fontSize: 16, fontWeight: FontWeight.w600, fontFamily: 'Poppins', color: AppColors.primaryColor, decoration: TextDecoration.underline, ), ), ), ], ), const SizedBox(height: 30), // Verify Button CustomButton( borderRadius: 10, height: 50, isloading: widget.isSimpleVerification!=null && widget.isSimpleVerification! ?smsprovider.loader:provider.loader, loadercolor: Colors.white, onClick: _verifyOtp, title: 'Verify Code', width: MediaQuery.of(context).size.width , backgroundColor: isDark ? AppColors.darkPrimaryColor : AppColors.primaryColor, ), ], ), ), ), ), ); } Widget _buildOtpField(int index) { final isDark = Theme.of(context).brightness == Brightness.dark; final hasValue = _controllers[index].text.isNotEmpty; return Container( width: 80, height: 60, decoration: BoxDecoration( borderRadius: BorderRadius.circular(10), border: Border.all( color: hasValue ? (isDark ? AppColors.darkPrimaryColor : AppColors.primaryColor) : (isDark ? AppColors.darkSecondaryText : Colors.grey.shade300), width: hasValue ? 2 : 1, ), color: isDark ? AppColors.blackBackgroundColor : AppColors.whiteBackgroundColor, boxShadow: hasValue ? [ BoxShadow( color: (isDark ? AppColors.darkPrimaryColor : AppColors.primaryColor) .withOpacity(0.2), blurRadius: 8, offset: const Offset(0, 2), ), ] : null, ), child: TextField( controller: _controllers[index], focusNode: _focusNodes[index], textAlign: TextAlign.center, textAlignVertical: TextAlignVertical.center, keyboardType: TextInputType.number, maxLength: 1, style: TextStyle( fontSize: 24, fontWeight: FontWeight.bold, fontFamily: 'Poppins', height: 0, color: isDark ? AppColors.whiteBackgroundColor : AppColors.blackBackgroundColor, ), inputFormatters: [FilteringTextInputFormatter.digitsOnly], decoration: const InputDecoration( border: InputBorder.none, counterText: '', contentPadding: const EdgeInsets.symmetric(vertical: 12), hintText: '-', hintStyle: TextStyle( fontSize: 24, fontWeight: FontWeight.w400, fontFamily: 'Poppins', height: 0, ), ), onChanged: (value) => _onOtpChanged(index, value), onTap: () { if (_controllers[index]!=null && _controllers[index].text.isEmpty) { // Find the first empty field and focus it for (int i = 0; i < 5; i++) { if (_controllers[i]!=null && _controllers[i].text.isEmpty) { _focusNodes[i].requestFocus(); break; } } } }, ), ); } }