|  |  |  | @ -6,6 +6,7 @@ import 'package:flutter/material.dart'; | 
		
	
		
			
				|  |  |  |  | import 'package:flutter/rendering.dart'; | 
		
	
		
			
				|  |  |  |  | import 'package:flutter/services.dart'; | 
		
	
		
			
				|  |  |  |  | import 'package:hmg_patient_app_new/core/utils/size_utils.dart'; | 
		
	
		
			
				|  |  |  |  | import 'package:hmg_patient_app_new/extensions/widget_extensions.dart'; | 
		
	
		
			
				|  |  |  |  | import 'package:hmg_patient_app_new/theme/colors.dart'; | 
		
	
		
			
				|  |  |  |  | import 'package:hmg_patient_app_new/widgets/appbar/app_bar_widget.dart'; | 
		
	
		
			
				|  |  |  |  | 
 | 
		
	
	
		
			
				
					|  |  |  | @ -50,7 +51,7 @@ class OTPWidget extends StatefulWidget { | 
		
	
		
			
				|  |  |  |  |   final TextInputType keyboardType; | 
		
	
		
			
				|  |  |  |  |   final EdgeInsets pinBoxOuterPadding; | 
		
	
		
			
				|  |  |  |  | 
 | 
		
	
		
			
				|  |  |  |  |   OTPWidget({ | 
		
	
		
			
				|  |  |  |  |   const OTPWidget({ | 
		
	
		
			
				|  |  |  |  |     Key? key, | 
		
	
		
			
				|  |  |  |  |     this.maxLength = 4, | 
		
	
		
			
				|  |  |  |  |     this.controller, | 
		
	
	
		
			
				
					|  |  |  | @ -101,7 +102,7 @@ class OTPWidgetState extends State<OTPWidget> with SingleTickerProviderStateMixi | 
		
	
		
			
				|  |  |  |  |       }); | 
		
	
		
			
				|  |  |  |  |       widget.controller?.text = text; | 
		
	
		
			
				|  |  |  |  |       widget.controller?.selection = TextSelection.collapsed(offset: text.length); | 
		
	
		
			
				|  |  |  |  |     } else if (oldWidget.maxLength > widget.maxLength && widget.maxLength > 0 && text.length > 0 && text.length > widget.maxLength) { | 
		
	
		
			
				|  |  |  |  |     } else if (oldWidget.maxLength > widget.maxLength && widget.maxLength > 0 && text.isNotEmpty && text.length > widget.maxLength) { | 
		
	
		
			
				|  |  |  |  |       setState(() { | 
		
	
		
			
				|  |  |  |  |         text = text.substring(0, widget.maxLength); | 
		
	
		
			
				|  |  |  |  |         currentIndex = text.length; | 
		
	
	
		
			
				
					|  |  |  | @ -127,7 +128,9 @@ class OTPWidgetState extends State<OTPWidget> with SingleTickerProviderStateMixi | 
		
	
		
			
				|  |  |  |  |     _highlightAnimationController = AnimationController(vsync: this); | 
		
	
		
			
				|  |  |  |  |     _initTextController(); | 
		
	
		
			
				|  |  |  |  |     _calculateStrList(); | 
		
	
		
			
				|  |  |  |  |     if (widget.controller != null) { | 
		
	
		
			
				|  |  |  |  |       widget.controller!.addListener(_controllerListener); | 
		
	
		
			
				|  |  |  |  |     } | 
		
	
		
			
				|  |  |  |  |     focusNode.addListener(_focusListener); | 
		
	
		
			
				|  |  |  |  |   } | 
		
	
		
			
				|  |  |  |  | 
 | 
		
	
	
		
			
				
					|  |  |  | @ -190,7 +193,9 @@ class OTPWidgetState extends State<OTPWidget> with SingleTickerProviderStateMixi | 
		
	
		
			
				|  |  |  |  |       focusNode.removeListener(_focusListener); | 
		
	
		
			
				|  |  |  |  |     } | 
		
	
		
			
				|  |  |  |  |     _highlightAnimationController.dispose(); | 
		
	
		
			
				|  |  |  |  |     widget.controller?.removeListener(_controllerListener); | 
		
	
		
			
				|  |  |  |  |     if (widget.controller != null) { | 
		
	
		
			
				|  |  |  |  |       widget.controller!.removeListener(_controllerListener); | 
		
	
		
			
				|  |  |  |  |     } | 
		
	
		
			
				|  |  |  |  | 
 | 
		
	
		
			
				|  |  |  |  |     super.dispose(); | 
		
	
		
			
				|  |  |  |  |   } | 
		
	
	
		
			
				
					|  |  |  | @ -241,12 +246,7 @@ class OTPWidgetState extends State<OTPWidget> with SingleTickerProviderStateMixi | 
		
	
		
			
				|  |  |  |  |         controller: widget.controller, | 
		
	
		
			
				|  |  |  |  |         keyboardType: widget.keyboardType, | 
		
	
		
			
				|  |  |  |  |         inputFormatters: widget.keyboardType == TextInputType.number ? <TextInputFormatter>[FilteringTextInputFormatter.digitsOnly] : null, | 
		
	
		
			
				|  |  |  |  |         // Enable SMS autofill | 
		
	
		
			
				|  |  |  |  |         autofillHints: const [AutofillHints.oneTimeCode], | 
		
	
		
			
				|  |  |  |  |         style: TextStyle( | 
		
	
		
			
				|  |  |  |  |           height: 0.1, | 
		
	
		
			
				|  |  |  |  |           color: Colors.transparent, | 
		
	
		
			
				|  |  |  |  |         ), | 
		
	
		
			
				|  |  |  |  |         style: TextStyle(height: 0.1, color: Colors.transparent), | 
		
	
		
			
				|  |  |  |  |         decoration: InputDecoration( | 
		
	
		
			
				|  |  |  |  |           contentPadding: EdgeInsets.all(0), | 
		
	
		
			
				|  |  |  |  |           focusedErrorBorder: transparentBorder, | 
		
	
	
		
			
				
					|  |  |  | @ -256,10 +256,7 @@ class OTPWidgetState extends State<OTPWidget> with SingleTickerProviderStateMixi | 
		
	
		
			
				|  |  |  |  |           focusedBorder: transparentBorder, | 
		
	
		
			
				|  |  |  |  |           counterText: null, | 
		
	
		
			
				|  |  |  |  |           counterStyle: null, | 
		
	
		
			
				|  |  |  |  |           helperStyle: TextStyle( | 
		
	
		
			
				|  |  |  |  |             height: 0.0, | 
		
	
		
			
				|  |  |  |  |             color: Colors.transparent, | 
		
	
		
			
				|  |  |  |  |           ), | 
		
	
		
			
				|  |  |  |  |           helperStyle: TextStyle(height: 0.0, color: Colors.transparent), | 
		
	
		
			
				|  |  |  |  |           labelStyle: TextStyle(height: 0.1), | 
		
	
		
			
				|  |  |  |  |           fillColor: Colors.transparent, | 
		
	
		
			
				|  |  |  |  |           border: InputBorder.none, | 
		
	
	
		
			
				
					|  |  |  | @ -307,25 +304,19 @@ class OTPWidgetState extends State<OTPWidget> with SingleTickerProviderStateMixi | 
		
	
		
			
				|  |  |  |  |     ); | 
		
	
		
			
				|  |  |  |  |   } | 
		
	
		
			
				|  |  |  |  | 
 | 
		
	
		
			
				|  |  |  |  | 
 | 
		
	
		
			
				|  |  |  |  |   Widget _buildPinCode(int i, BuildContext context) { | 
		
	
		
			
				|  |  |  |  |     Color borderColor; | 
		
	
		
			
				|  |  |  |  |     Color pinBoxColor; | 
		
	
		
			
				|  |  |  |  | 
 | 
		
	
		
			
				|  |  |  |  |     // Determine if OTP is complete | 
		
	
		
			
				|  |  |  |  |     bool isComplete = text.length == widget.maxLength; | 
		
	
		
			
				|  |  |  |  | 
 | 
		
	
		
			
				|  |  |  |  |     if (widget.hasError) { | 
		
	
		
			
				|  |  |  |  |       borderColor = widget.errorBorderColor; | 
		
	
		
			
				|  |  |  |  |       pinBoxColor = widget.pinBoxColor; | 
		
	
		
			
				|  |  |  |  |     } else if (isComplete) { | 
		
	
		
			
				|  |  |  |  |       borderColor = Colors.transparent; | 
		
	
		
			
				|  |  |  |  |       pinBoxColor = widget.errorBorderColor; | 
		
	
		
			
				|  |  |  |  |     } else if (text.length == widget.maxLength) { | 
		
	
		
			
				|  |  |  |  |       // Check for completion first, before individual box logic | 
		
	
		
			
				|  |  |  |  |       pinBoxColor = AppColors.successColor; | 
		
	
		
			
				|  |  |  |  |     } else if (i < text.length) { | 
		
	
		
			
				|  |  |  |  |       borderColor = Colors.transparent; | 
		
	
		
			
				|  |  |  |  |       pinBoxColor = AppColors.blackBgColor; | 
		
	
		
			
				|  |  |  |  |       pinBoxColor = AppColors.blackBgColor; // Custom color for filled boxes | 
		
	
		
			
				|  |  |  |  |     } else { | 
		
	
		
			
				|  |  |  |  |       borderColor = Colors.transparent; | 
		
	
		
			
				|  |  |  |  |       pinBoxColor = widget.pinBoxColor; | 
		
	
		
			
				|  |  |  |  |       pinBoxColor = widget.pinBoxColor; // Default white color | 
		
	
		
			
				|  |  |  |  |     } | 
		
	
		
			
				|  |  |  |  | 
 | 
		
	
		
			
				|  |  |  |  |     EdgeInsets insets; | 
		
	
	
		
			
				
					|  |  |  | @ -346,18 +337,17 @@ class OTPWidgetState extends State<OTPWidget> with SingleTickerProviderStateMixi | 
		
	
		
			
				|  |  |  |  |     } else { | 
		
	
		
			
				|  |  |  |  |       insets = widget.pinBoxOuterPadding; | 
		
	
		
			
				|  |  |  |  |     } | 
		
	
		
			
				|  |  |  |  |     return Container( | 
		
	
		
			
				|  |  |  |  | 
 | 
		
	
		
			
				|  |  |  |  |     return AnimatedContainer( | 
		
	
		
			
				|  |  |  |  |       duration: const Duration(milliseconds: 200), | 
		
	
		
			
				|  |  |  |  |       curve: Curves.easeInOut, | 
		
	
		
			
				|  |  |  |  |       key: ValueKey<String>("container$i"), | 
		
	
		
			
				|  |  |  |  |       alignment: Alignment.center, | 
		
	
		
			
				|  |  |  |  |       padding: EdgeInsets.symmetric(vertical: 4.0, horizontal: 1.0), | 
		
	
		
			
				|  |  |  |  |       margin: insets, | 
		
	
		
			
				|  |  |  |  |       decoration: BoxDecoration( | 
		
	
		
			
				|  |  |  |  |         border: Border.all( | 
		
	
		
			
				|  |  |  |  |           color: borderColor, | 
		
	
		
			
				|  |  |  |  |           width: widget.pinBoxBorderWidth, | 
		
	
		
			
				|  |  |  |  |         ), | 
		
	
		
			
				|  |  |  |  |       decoration: RoundedRectangleBorder().toSmoothCornerDecoration( | 
		
	
		
			
				|  |  |  |  |         color: pinBoxColor, | 
		
	
		
			
				|  |  |  |  |         borderRadius: BorderRadius.circular(widget.pinBoxRadius), | 
		
	
		
			
				|  |  |  |  |         borderRadius: widget.pinBoxRadius, | 
		
	
		
			
				|  |  |  |  |       ), | 
		
	
		
			
				|  |  |  |  |       width: widget.pinBoxWidth, | 
		
	
		
			
				|  |  |  |  |       height: widget.pinBoxHeight, | 
		
	
	
		
			
				
					|  |  |  | @ -365,6 +355,59 @@ class OTPWidgetState extends State<OTPWidget> with SingleTickerProviderStateMixi | 
		
	
		
			
				|  |  |  |  |     ); | 
		
	
		
			
				|  |  |  |  |   } | 
		
	
		
			
				|  |  |  |  | 
 | 
		
	
		
			
				|  |  |  |  | 
 | 
		
	
		
			
				|  |  |  |  |   // Widget _buildPinCode(int i, BuildContext context) { | 
		
	
		
			
				|  |  |  |  |   //   Color pinBoxColor = widget.pinBoxColor; | 
		
	
		
			
				|  |  |  |  |   // | 
		
	
		
			
				|  |  |  |  |   //   if (widget.hasError) { | 
		
	
		
			
				|  |  |  |  |   //     pinBoxColor = widget.errorBorderColor; | 
		
	
		
			
				|  |  |  |  |   //   } else if (i < text.length) { | 
		
	
		
			
				|  |  |  |  |   //     pinBoxColor = AppColors.blackBgColor; // Custom color for filled boxes | 
		
	
		
			
				|  |  |  |  |   //   } else { | 
		
	
		
			
				|  |  |  |  |   //     pinBoxColor = widget.pinBoxColor; | 
		
	
		
			
				|  |  |  |  |   //   } | 
		
	
		
			
				|  |  |  |  |   // | 
		
	
		
			
				|  |  |  |  |   //   // Change color to success when all fields are complete | 
		
	
		
			
				|  |  |  |  |   //   if (text.length == widget.maxLength) { | 
		
	
		
			
				|  |  |  |  |   //     pinBoxColor = AppColors.successColor; | 
		
	
		
			
				|  |  |  |  |   //   } | 
		
	
		
			
				|  |  |  |  |   // | 
		
	
		
			
				|  |  |  |  |   //   EdgeInsets insets; | 
		
	
		
			
				|  |  |  |  |   //   if (i == 0) { | 
		
	
		
			
				|  |  |  |  |   //     insets = EdgeInsets.only( | 
		
	
		
			
				|  |  |  |  |   //       left: 0, | 
		
	
		
			
				|  |  |  |  |   //       top: widget.pinBoxOuterPadding.top, | 
		
	
		
			
				|  |  |  |  |   //       right: widget.pinBoxOuterPadding.right, | 
		
	
		
			
				|  |  |  |  |   //       bottom: widget.pinBoxOuterPadding.bottom, | 
		
	
		
			
				|  |  |  |  |   //     ); | 
		
	
		
			
				|  |  |  |  |   //   } else if (i == strList.length - 1) { | 
		
	
		
			
				|  |  |  |  |   //     insets = EdgeInsets.only( | 
		
	
		
			
				|  |  |  |  |   //       left: widget.pinBoxOuterPadding.left, | 
		
	
		
			
				|  |  |  |  |   //       top: widget.pinBoxOuterPadding.top, | 
		
	
		
			
				|  |  |  |  |   //       right: 0, | 
		
	
		
			
				|  |  |  |  |   //       bottom: widget.pinBoxOuterPadding.bottom, | 
		
	
		
			
				|  |  |  |  |   //     ); | 
		
	
		
			
				|  |  |  |  |   //   } else { | 
		
	
		
			
				|  |  |  |  |   //     insets = widget.pinBoxOuterPadding; | 
		
	
		
			
				|  |  |  |  |   //   } | 
		
	
		
			
				|  |  |  |  |   // | 
		
	
		
			
				|  |  |  |  |   //   return AnimatedContainer( | 
		
	
		
			
				|  |  |  |  |   //     duration: const Duration(milliseconds: 200), | 
		
	
		
			
				|  |  |  |  |   //     curve: Curves.easeInOut, | 
		
	
		
			
				|  |  |  |  |   //     key: ValueKey<String>("container$i"), | 
		
	
		
			
				|  |  |  |  |   //     alignment: Alignment.center, | 
		
	
		
			
				|  |  |  |  |   //     padding: EdgeInsets.symmetric(vertical: 4.0, horizontal: 1.0), | 
		
	
		
			
				|  |  |  |  |   //     margin: insets, | 
		
	
		
			
				|  |  |  |  |   //     decoration: RoundedRectangleBorder().toSmoothCornerDecoration( | 
		
	
		
			
				|  |  |  |  |   //       color: pinBoxColor, | 
		
	
		
			
				|  |  |  |  |   //       borderRadius: widget.pinBoxRadius, | 
		
	
		
			
				|  |  |  |  |   //     ), | 
		
	
		
			
				|  |  |  |  |   //     width: widget.pinBoxWidth, | 
		
	
		
			
				|  |  |  |  |   //     height: widget.pinBoxHeight, | 
		
	
		
			
				|  |  |  |  |   //     child: _animatedTextBox(strList[i], i), | 
		
	
		
			
				|  |  |  |  |   //   ); | 
		
	
		
			
				|  |  |  |  |   // } | 
		
	
		
			
				|  |  |  |  | 
 | 
		
	
		
			
				|  |  |  |  |   Widget _animatedTextBox(String text, int i) { | 
		
	
		
			
				|  |  |  |  |     if (widget.pinTextAnimatedSwitcherTransition != null) { | 
		
	
		
			
				|  |  |  |  |       return AnimatedSwitcher( | 
		
	
	
		
			
				
					|  |  |  | @ -407,10 +450,12 @@ class OTPVerificationScreen extends StatefulWidget { | 
		
	
		
			
				|  |  |  |  | 
 | 
		
	
		
			
				|  |  |  |  | class _OTPVerificationScreenState extends State<OTPVerificationScreen> { | 
		
	
		
			
				|  |  |  |  |   final int _otpLength = 4; | 
		
	
		
			
				|  |  |  |  |   late TextEditingController _otpController; | 
		
	
		
			
				|  |  |  |  |   late final TextEditingController _otpController; | 
		
	
		
			
				|  |  |  |  | 
 | 
		
	
		
			
				|  |  |  |  |   Timer? _resendTimer; | 
		
	
		
			
				|  |  |  |  |   int _resendTime = 60; | 
		
	
		
			
				|  |  |  |  |   int _resendTime = 120; | 
		
	
		
			
				|  |  |  |  |   bool _isOtpComplete = false; | 
		
	
		
			
				|  |  |  |  |   bool _isVerifying = false; // Flag to prevent multiple verification calls | 
		
	
		
			
				|  |  |  |  | 
 | 
		
	
		
			
				|  |  |  |  |   @override | 
		
	
		
			
				|  |  |  |  |   void initState() { | 
		
	
	
		
			
				
					|  |  |  | @ -437,30 +482,29 @@ class _OTPVerificationScreenState extends State<OTPVerificationScreen> { | 
		
	
		
			
				|  |  |  |  |   } | 
		
	
		
			
				|  |  |  |  | 
 | 
		
	
		
			
				|  |  |  |  |   void _onOtpChanged(String value) { | 
		
	
		
			
				|  |  |  |  |     // Handle clipboard paste or programmatic input | 
		
	
		
			
				|  |  |  |  |     if (value.length >= 4) { | 
		
	
		
			
				|  |  |  |  |       _onOtpCompleted(value); | 
		
	
		
			
				|  |  |  |  |       // String? otp = _extractOtpFromText(value); | 
		
	
		
			
				|  |  |  |  |       // if (otp != null) { | 
		
	
		
			
				|  |  |  |  |       //   autoFillOtp(otp); | 
		
	
		
			
				|  |  |  |  |       //   return; | 
		
	
		
			
				|  |  |  |  |       // } | 
		
	
		
			
				|  |  |  |  |     } | 
		
	
		
			
				|  |  |  |  |     setState(() { | 
		
	
		
			
				|  |  |  |  |       _isOtpComplete = value.length == _otpLength; | 
		
	
		
			
				|  |  |  |  |     }); | 
		
	
		
			
				|  |  |  |  | 
 | 
		
	
		
			
				|  |  |  |  |     // The OTPWidget will automatically call onDone when complete | 
		
	
		
			
				|  |  |  |  |     // This method can be used for any additional logic on text change | 
		
	
		
			
				|  |  |  |  |     if (_isOtpComplete && !_isVerifying) { | 
		
	
		
			
				|  |  |  |  |       _isVerifying = true; | 
		
	
		
			
				|  |  |  |  |       _verifyOtp(value); | 
		
	
		
			
				|  |  |  |  |     } else if (!_isOtpComplete) { | 
		
	
		
			
				|  |  |  |  |       // Reset the flag when OTP is incomplete (user is editing) | 
		
	
		
			
				|  |  |  |  |       _isVerifying = false; | 
		
	
		
			
				|  |  |  |  |     } | 
		
	
		
			
				|  |  |  |  | 
 | 
		
	
		
			
				|  |  |  |  |   void _onOtpCompleted(String otp) { | 
		
	
		
			
				|  |  |  |  |     debugPrint('OTP Completed: $otp'); | 
		
	
		
			
				|  |  |  |  |     widget.checkActivationCode(int.parse(otp)); | 
		
	
		
			
				|  |  |  |  |   } | 
		
	
		
			
				|  |  |  |  | 
 | 
		
	
		
			
				|  |  |  |  |   void _resendOtp() { | 
		
	
		
			
				|  |  |  |  |     if (_resendTime == 0) { | 
		
	
		
			
				|  |  |  |  |       setState(() => _resendTime = 60); | 
		
	
		
			
				|  |  |  |  |       setState(() { | 
		
	
		
			
				|  |  |  |  |         _resendTime = 120; | 
		
	
		
			
				|  |  |  |  |         _isVerifying = false; | 
		
	
		
			
				|  |  |  |  |         _isOtpComplete = false; | 
		
	
		
			
				|  |  |  |  |       }); | 
		
	
		
			
				|  |  |  |  |       _otpController.clear(); | 
		
	
		
			
				|  |  |  |  |       _startResendTimer(); | 
		
	
		
			
				|  |  |  |  |       autoFillOtp("1234"); | 
		
	
		
			
				|  |  |  |  |       // autoFillOtp("1234"); | 
		
	
		
			
				|  |  |  |  |       widget.onResendOTPPressed(widget.phoneNumber); | 
		
	
		
			
				|  |  |  |  |     } | 
		
	
		
			
				|  |  |  |  |   } | 
		
	
	
		
			
				
					|  |  |  | @ -470,68 +514,6 @@ class _OTPVerificationScreenState extends State<OTPVerificationScreen> { | 
		
	
		
			
				|  |  |  |  |     return phone.length > 4 ? '05xxxxxx${phone.substring(phone.length - 2)}' : phone; | 
		
	
		
			
				|  |  |  |  |   } | 
		
	
		
			
				|  |  |  |  | 
 | 
		
	
		
			
				|  |  |  |  |   /// Extract OTP from text using multiple patterns | 
		
	
		
			
				|  |  |  |  |   String? _extractOtpFromText(String text) { | 
		
	
		
			
				|  |  |  |  |     // Pattern 1: Find 4-6 consecutive digits | 
		
	
		
			
				|  |  |  |  |     RegExp digitPattern = RegExp(r'\b\d{4,6}\b'); | 
		
	
		
			
				|  |  |  |  |     Match? match = digitPattern.firstMatch(text); | 
		
	
		
			
				|  |  |  |  |      | 
		
	
		
			
				|  |  |  |  |     if (match != null) { | 
		
	
		
			
				|  |  |  |  |       String digits = match.group(0)!; | 
		
	
		
			
				|  |  |  |  |       if (digits.length >= _otpLength) { | 
		
	
		
			
				|  |  |  |  |         return digits.substring(0, _otpLength); | 
		
	
		
			
				|  |  |  |  |       } | 
		
	
		
			
				|  |  |  |  |     } | 
		
	
		
			
				|  |  |  |  |      | 
		
	
		
			
				|  |  |  |  |     // Pattern 2: Find digits separated by spaces or special characters | 
		
	
		
			
				|  |  |  |  |     String cleanedText = text.replaceAll(RegExp(r'[^\d]'), ''); | 
		
	
		
			
				|  |  |  |  |     if (cleanedText.length >= _otpLength) { | 
		
	
		
			
				|  |  |  |  |       return cleanedText.substring(0, _otpLength); | 
		
	
		
			
				|  |  |  |  |     } | 
		
	
		
			
				|  |  |  |  |      | 
		
	
		
			
				|  |  |  |  |     return null; | 
		
	
		
			
				|  |  |  |  |   } | 
		
	
		
			
				|  |  |  |  | 
 | 
		
	
		
			
				|  |  |  |  |   /// Paste OTP from clipboard | 
		
	
		
			
				|  |  |  |  |   Future<void> _pasteFromClipboard() async { | 
		
	
		
			
				|  |  |  |  |     try { | 
		
	
		
			
				|  |  |  |  |       ClipboardData? data = await Clipboard.getData(Clipboard.kTextPlain); | 
		
	
		
			
				|  |  |  |  |       if (data != null && data.text != null) { | 
		
	
		
			
				|  |  |  |  |         String clipboardText = data.text!; | 
		
	
		
			
				|  |  |  |  |         String? otp = _extractOtpFromText(clipboardText); | 
		
	
		
			
				|  |  |  |  |          | 
		
	
		
			
				|  |  |  |  |         if (otp != null) { | 
		
	
		
			
				|  |  |  |  |           autoFillOtp(otp); | 
		
	
		
			
				|  |  |  |  |            | 
		
	
		
			
				|  |  |  |  |           // Show feedback to user | 
		
	
		
			
				|  |  |  |  |           ScaffoldMessenger.of(context).showSnackBar( | 
		
	
		
			
				|  |  |  |  |             SnackBar( | 
		
	
		
			
				|  |  |  |  |               content: Text('OTP pasted: $otp'), | 
		
	
		
			
				|  |  |  |  |               duration: const Duration(seconds: 2), | 
		
	
		
			
				|  |  |  |  |               backgroundColor: AppColors.successColor, | 
		
	
		
			
				|  |  |  |  |             ), | 
		
	
		
			
				|  |  |  |  |           ); | 
		
	
		
			
				|  |  |  |  |         } else { | 
		
	
		
			
				|  |  |  |  |           // Show error if no valid OTP found | 
		
	
		
			
				|  |  |  |  |           ScaffoldMessenger.of(context).showSnackBar( | 
		
	
		
			
				|  |  |  |  |             const SnackBar( | 
		
	
		
			
				|  |  |  |  |               content: Text('No valid OTP found in clipboard'), | 
		
	
		
			
				|  |  |  |  |               duration: Duration(seconds: 2), | 
		
	
		
			
				|  |  |  |  |             ), | 
		
	
		
			
				|  |  |  |  |           ); | 
		
	
		
			
				|  |  |  |  |         } | 
		
	
		
			
				|  |  |  |  |       } | 
		
	
		
			
				|  |  |  |  |     } catch (e) { | 
		
	
		
			
				|  |  |  |  |       debugPrint('Error pasting from clipboard: $e'); | 
		
	
		
			
				|  |  |  |  |       ScaffoldMessenger.of(context).showSnackBar( | 
		
	
		
			
				|  |  |  |  |         const SnackBar( | 
		
	
		
			
				|  |  |  |  |           content: Text('Failed to paste from clipboard'), | 
		
	
		
			
				|  |  |  |  |           duration: Duration(seconds: 2), | 
		
	
		
			
				|  |  |  |  |         ), | 
		
	
		
			
				|  |  |  |  |       ); | 
		
	
		
			
				|  |  |  |  |     } | 
		
	
		
			
				|  |  |  |  |   } | 
		
	
		
			
				|  |  |  |  | 
 | 
		
	
		
			
				|  |  |  |  |   @override | 
		
	
		
			
				|  |  |  |  |   Widget build(BuildContext context) { | 
		
	
		
			
				|  |  |  |  |     return Scaffold( | 
		
	
	
		
			
				
					|  |  |  | @ -563,33 +545,28 @@ class _OTPVerificationScreenState extends State<OTPVerificationScreen> { | 
		
	
		
			
				|  |  |  |  | 
 | 
		
	
		
			
				|  |  |  |  |               // OTP Input Fields using new OTP Widget | 
		
	
		
			
				|  |  |  |  |               Center( | 
		
	
		
			
				|  |  |  |  |                 child: AutofillGroup( | 
		
	
		
			
				|  |  |  |  |                 child: OTPWidget( | 
		
	
		
			
				|  |  |  |  |                   maxLength: _otpLength, | 
		
	
		
			
				|  |  |  |  |                   controller: _otpController, | 
		
	
		
			
				|  |  |  |  |                     pinBoxWidth: 75.h, | 
		
	
		
			
				|  |  |  |  |                     pinBoxHeight: 100.h, | 
		
	
		
			
				|  |  |  |  |                     autoFocus: true, | 
		
	
		
			
				|  |  |  |  |                   pinBoxWidth: 70.h, | 
		
	
		
			
				|  |  |  |  |                   pinBoxHeight: 100, | 
		
	
		
			
				|  |  |  |  |                   pinBoxRadius: 16, | 
		
	
		
			
				|  |  |  |  |                   pinBoxBorderWidth: 0, | 
		
	
		
			
				|  |  |  |  |                   pinBoxOuterPadding: EdgeInsets.symmetric(horizontal: 4.h), | 
		
	
		
			
				|  |  |  |  |                   defaultBorderColor: Colors.transparent, | 
		
	
		
			
				|  |  |  |  |                   textBorderColor: Colors.transparent, | 
		
	
		
			
				|  |  |  |  |                     errorBorderColor: AppColors.primaryRedColor, | 
		
	
		
			
				|  |  |  |  |                   pinBoxColor: AppColors.whiteColor, | 
		
	
		
			
				|  |  |  |  |                   autoFocus: true, | 
		
	
		
			
				|  |  |  |  |                   onTextChanged: _onOtpChanged, | 
		
	
		
			
				|  |  |  |  |                   pinTextStyle: TextStyle( | 
		
	
		
			
				|  |  |  |  |                       fontSize: 50.fSize, | 
		
	
		
			
				|  |  |  |  |                     fontSize: 40.fSize, | 
		
	
		
			
				|  |  |  |  |                     fontWeight: FontWeight.bold, | 
		
	
		
			
				|  |  |  |  |                     color: AppColors.whiteColor, | 
		
	
		
			
				|  |  |  |  |                   ), | 
		
	
		
			
				|  |  |  |  |                     onTextChanged: _onOtpChanged, | 
		
	
		
			
				|  |  |  |  |                     onDone: _onOtpCompleted, | 
		
	
		
			
				|  |  |  |  |                   ), | 
		
	
		
			
				|  |  |  |  |                 ), | 
		
	
		
			
				|  |  |  |  |               ), | 
		
	
		
			
				|  |  |  |  | 
 | 
		
	
		
			
				|  |  |  |  |               const SizedBox(height: 16), | 
		
	
		
			
				|  |  |  |  | 
 | 
		
	
		
			
				|  |  |  |  |               const SizedBox(height: 32), | 
		
	
		
			
				|  |  |  |  | 
 | 
		
	
		
			
				|  |  |  |  |               // Resend OTP | 
		
	
		
			
				|  |  |  |  |               Row( | 
		
	
	
		
			
				
					|  |  |  | @ -597,9 +574,18 @@ class _OTPVerificationScreenState extends State<OTPVerificationScreen> { | 
		
	
		
			
				|  |  |  |  |                 children: [ | 
		
	
		
			
				|  |  |  |  |                   const Text("Didn't receive it? "), | 
		
	
		
			
				|  |  |  |  |                   if (_resendTime > 0) | 
		
	
		
			
				|  |  |  |  |                     Text( | 
		
	
		
			
				|  |  |  |  |                       'resend in (${_resendTime.toString().padLeft(2, '0')}:00). ', | 
		
	
		
			
				|  |  |  |  |                     Builder( | 
		
	
		
			
				|  |  |  |  |                       // Use a Builder to easily calculate minutes and seconds inline | 
		
	
		
			
				|  |  |  |  |                       builder: (context) { | 
		
	
		
			
				|  |  |  |  |                         final minutes = (_resendTime ~/ 60) | 
		
	
		
			
				|  |  |  |  |                             .toString() | 
		
	
		
			
				|  |  |  |  |                             .padLeft(2, '0'); // Integer division for minutes          final seconds = (_resendTime % 60).toString().padLeft(2, '0'); // Modulo for remaining seconds | 
		
	
		
			
				|  |  |  |  |                         final seconds = (_resendTime % 60).toString().padLeft(2, '0'); // Modulo for remaining seconds // <--- HERE IT IS | 
		
	
		
			
				|  |  |  |  |                         return Text( | 
		
	
		
			
				|  |  |  |  |                           'resend in ($minutes:$seconds). ', | 
		
	
		
			
				|  |  |  |  |                           style: const TextStyle(color: Colors.grey), | 
		
	
		
			
				|  |  |  |  |                         ); | 
		
	
		
			
				|  |  |  |  |                       }, | 
		
	
		
			
				|  |  |  |  |                     ) | 
		
	
		
			
				|  |  |  |  |                   else | 
		
	
		
			
				|  |  |  |  |                     GestureDetector( | 
		
	
	
		
			
				
					|  |  |  | @ -621,84 +607,16 @@ class _OTPVerificationScreenState extends State<OTPVerificationScreen> { | 
		
	
		
			
				|  |  |  |  |     ); | 
		
	
		
			
				|  |  |  |  |   } | 
		
	
		
			
				|  |  |  |  | 
 | 
		
	
		
			
				|  |  |  |  |   void _verifyOtp(String otp) { | 
		
	
		
			
				|  |  |  |  |     debugPrint('Verifying OTP: $otp'); | 
		
	
		
			
				|  |  |  |  |     widget.checkActivationCode(int.parse(otp)); | 
		
	
		
			
				|  |  |  |  |   } | 
		
	
		
			
				|  |  |  |  | 
 | 
		
	
		
			
				|  |  |  |  |   /// Auto fill OTP into text fields | 
		
	
		
			
				|  |  |  |  |   void autoFillOtp(String otp) { | 
		
	
		
			
				|  |  |  |  |     if (!mounted) return; | 
		
	
		
			
				|  |  |  |  |     if (otp.length != _otpLength) return; | 
		
	
		
			
				|  |  |  |  |      | 
		
	
		
			
				|  |  |  |  |     try { | 
		
	
		
			
				|  |  |  |  |       // Clear any existing text first | 
		
	
		
			
				|  |  |  |  |       _otpController.clear(); | 
		
	
		
			
				|  |  |  |  | 
 | 
		
	
		
			
				|  |  |  |  |       // Use WidgetsBinding to ensure the widget tree is ready | 
		
	
		
			
				|  |  |  |  |       WidgetsBinding.instance.addPostFrameCallback((_) { | 
		
	
		
			
				|  |  |  |  |         if (!mounted) return; | 
		
	
		
			
				|  |  |  |  | 
 | 
		
	
		
			
				|  |  |  |  |         try { | 
		
	
		
			
				|  |  |  |  |           // Set the text first | 
		
	
		
			
				|  |  |  |  |     _isVerifying = false; | 
		
	
		
			
				|  |  |  |  |     _otpController.text = otp; | 
		
	
		
			
				|  |  |  |  | 
 | 
		
	
		
			
				|  |  |  |  |           // Use a longer delay for iOS and add validation | 
		
	
		
			
				|  |  |  |  |           Future.delayed(const Duration(milliseconds: 300), () { | 
		
	
		
			
				|  |  |  |  |             if (!mounted) return; | 
		
	
		
			
				|  |  |  |  | 
 | 
		
	
		
			
				|  |  |  |  |             try { | 
		
	
		
			
				|  |  |  |  |               // Only attempt to set selection if conditions are met | 
		
	
		
			
				|  |  |  |  |               if (_otpController.text == otp && | 
		
	
		
			
				|  |  |  |  |                   _otpController.text.length == _otpLength && | 
		
	
		
			
				|  |  |  |  |                   _otpController.text.length <= _otpController.text.length) { | 
		
	
		
			
				|  |  |  |  | 
 | 
		
	
		
			
				|  |  |  |  |                 final newSelection = TextSelection.fromPosition( | 
		
	
		
			
				|  |  |  |  |                   TextPosition(offset: _otpController.text.length), | 
		
	
		
			
				|  |  |  |  |                 ); | 
		
	
		
			
				|  |  |  |  | 
 | 
		
	
		
			
				|  |  |  |  |                 // Validate selection before setting | 
		
	
		
			
				|  |  |  |  |                 if (newSelection.baseOffset <= _otpController.text.length && | 
		
	
		
			
				|  |  |  |  |                     newSelection.extentOffset <= _otpController.text.length) { | 
		
	
		
			
				|  |  |  |  |                   _otpController.selection = newSelection; | 
		
	
		
			
				|  |  |  |  |                 } | 
		
	
		
			
				|  |  |  |  |               } | 
		
	
		
			
				|  |  |  |  |             } catch (selectionError) { | 
		
	
		
			
				|  |  |  |  |               // Silently fail on selection - text is already set correctly | 
		
	
		
			
				|  |  |  |  |               debugPrint('Selection error (non-critical): $selectionError'); | 
		
	
		
			
				|  |  |  |  |             } | 
		
	
		
			
				|  |  |  |  |           }); | 
		
	
		
			
				|  |  |  |  |         } catch (textError) { | 
		
	
		
			
				|  |  |  |  |           debugPrint('Error setting OTP text: $textError'); | 
		
	
		
			
				|  |  |  |  |         } | 
		
	
		
			
				|  |  |  |  |       }); | 
		
	
		
			
				|  |  |  |  |     } catch (e) { | 
		
	
		
			
				|  |  |  |  |       debugPrint('Error in autoFillOtp: $e'); | 
		
	
		
			
				|  |  |  |  |     } | 
		
	
		
			
				|  |  |  |  |   } | 
		
	
		
			
				|  |  |  |  | 
 | 
		
	
		
			
				|  |  |  |  |   /// Clear OTP fields | 
		
	
		
			
				|  |  |  |  |   void clearOtp() { | 
		
	
		
			
				|  |  |  |  |     _otpController.clear(); | 
		
	
		
			
				|  |  |  |  |   } | 
		
	
		
			
				|  |  |  |  | 
 | 
		
	
		
			
				|  |  |  |  |   /// Get current OTP value | 
		
	
		
			
				|  |  |  |  |   String getCurrentOtp() { | 
		
	
		
			
				|  |  |  |  |     return _otpController.text; | 
		
	
		
			
				|  |  |  |  |   } | 
		
	
		
			
				|  |  |  |  | 
 | 
		
	
		
			
				|  |  |  |  |   /// Check if OTP is complete | 
		
	
		
			
				|  |  |  |  |   bool isOtpComplete() { | 
		
	
		
			
				|  |  |  |  |     return _otpController.text.length == _otpLength; | 
		
	
		
			
				|  |  |  |  |   } | 
		
	
		
			
				|  |  |  |  | 
 | 
		
	
		
			
				|  |  |  |  |   /// Simulate SMS received with OTP (for testing purposes) | 
		
	
		
			
				|  |  |  |  |   void simulateSMSReceived(String otp) { | 
		
	
		
			
				|  |  |  |  |     if (otp.length == _otpLength && RegExp(r'^\d+$').hasMatch(otp)) { | 
		
	
		
			
				|  |  |  |  |       autoFillOtp(otp); | 
		
	
		
			
				|  |  |  |  |       // Show a brief indicator that SMS was detected | 
		
	
		
			
				|  |  |  |  |       ScaffoldMessenger.of(context).showSnackBar( | 
		
	
		
			
				|  |  |  |  |         SnackBar( | 
		
	
		
			
				|  |  |  |  |           content: Text('OTP detected from SMS: $otp'), | 
		
	
		
			
				|  |  |  |  |           duration: const Duration(seconds: 2), | 
		
	
		
			
				|  |  |  |  |           backgroundColor: AppColors.successColor, | 
		
	
		
			
				|  |  |  |  |         ), | 
		
	
		
			
				|  |  |  |  |       ); | 
		
	
		
			
				|  |  |  |  |     } | 
		
	
		
			
				|  |  |  |  |   } | 
		
	
		
			
				|  |  |  |  | } | 
		
	
	
		
			
				
					|  |  |  | 
 |