validations & otp widget & focus & auto fill
			#32
			
				
			
		
		
	 Merged
	
	
		
		
			
		
		
		
		
			
		
		
			
			
				Haroon6138
				merged 1 commits from dev_aamir  into master 2 months ago
			
		
	
	
| @ -1,377 +0,0 @@ | |||||||
| import 'dart:async'; |  | ||||||
| 
 |  | ||||||
| import 'package:flutter/animation.dart'; |  | ||||||
| import 'package:flutter/foundation.dart'; |  | ||||||
| import 'package:flutter/material.dart'; |  | ||||||
| import 'package:flutter/rendering.dart'; |  | ||||||
| import 'package:flutter/services.dart'; |  | ||||||
| 
 |  | ||||||
| typedef OnDone = void Function(String text); |  | ||||||
| 
 |  | ||||||
| class ProvidedPinBoxTextAnimation { |  | ||||||
|   static AnimatedSwitcherTransitionBuilder scalingTransition = (child, animation) { |  | ||||||
|     return ScaleTransition( |  | ||||||
|       child: child, |  | ||||||
|       scale: animation, |  | ||||||
|     ); |  | ||||||
|   }; |  | ||||||
| 
 |  | ||||||
|   static AnimatedSwitcherTransitionBuilder defaultNoTransition = (Widget child, Animation<double> animation) { |  | ||||||
|     return child; |  | ||||||
|   }; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| class OTPWidget extends StatefulWidget { |  | ||||||
|   final int maxLength; |  | ||||||
|   late TextEditingController? controller; |  | ||||||
| 
 |  | ||||||
|   final Color defaultBorderColor; |  | ||||||
|   final Color pinBoxColor; |  | ||||||
|   final double pinBoxBorderWidth; |  | ||||||
|   final double pinBoxRadius; |  | ||||||
|   final bool hideDefaultKeyboard; |  | ||||||
| 
 |  | ||||||
|   final TextStyle? pinTextStyle; |  | ||||||
|   final double pinBoxHeight; |  | ||||||
|   final double pinBoxWidth; |  | ||||||
|   final OnDone? onDone; |  | ||||||
|   final bool hasError; |  | ||||||
|   final Color errorBorderColor; |  | ||||||
|   final Color textBorderColor; |  | ||||||
|   final Function(String)? onTextChanged; |  | ||||||
|   final bool autoFocus; |  | ||||||
|   final FocusNode? focusNode; |  | ||||||
|   final AnimatedSwitcherTransitionBuilder? pinTextAnimatedSwitcherTransition; |  | ||||||
|   final Duration pinTextAnimatedSwitcherDuration; |  | ||||||
|   final TextDirection textDirection; |  | ||||||
|   final TextInputType keyboardType; |  | ||||||
|   final EdgeInsets pinBoxOuterPadding; |  | ||||||
| 
 |  | ||||||
|   OTPWidget({ |  | ||||||
|     Key? key, |  | ||||||
|     this.maxLength = 4, |  | ||||||
|     this.controller, |  | ||||||
|     this.pinBoxWidth = 70.0, |  | ||||||
|     this.pinBoxHeight = 70.0, |  | ||||||
|     this.pinTextStyle, |  | ||||||
|     this.onDone, |  | ||||||
|     this.defaultBorderColor = Colors.black, |  | ||||||
|     this.textBorderColor = Colors.black, |  | ||||||
|     this.pinTextAnimatedSwitcherTransition, |  | ||||||
|     this.pinTextAnimatedSwitcherDuration = const Duration(), |  | ||||||
|     this.hasError = false, |  | ||||||
|     this.errorBorderColor = Colors.red, |  | ||||||
|     this.onTextChanged, |  | ||||||
|     this.autoFocus = false, |  | ||||||
|     this.focusNode, |  | ||||||
|     this.textDirection = TextDirection.ltr, |  | ||||||
|     this.keyboardType = TextInputType.number, |  | ||||||
|     this.pinBoxOuterPadding = const EdgeInsets.symmetric(horizontal: 4.0), |  | ||||||
|     this.pinBoxColor = Colors.white, |  | ||||||
|     this.pinBoxBorderWidth = 2.0, |  | ||||||
|     this.pinBoxRadius = 0, |  | ||||||
|     this.hideDefaultKeyboard = false, |  | ||||||
|   }) : super(key: key); |  | ||||||
| 
 |  | ||||||
|   @override |  | ||||||
|   State<StatefulWidget> createState() { |  | ||||||
|     return OTPWidgetState(); |  | ||||||
|   } |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| class OTPWidgetState extends State<OTPWidget> with SingleTickerProviderStateMixin { |  | ||||||
|  late  AnimationController _highlightAnimationController; |  | ||||||
|   late FocusNode focusNode; |  | ||||||
|   String text = ""; |  | ||||||
|   int currentIndex = 0; |  | ||||||
|   List<String> strList = []; |  | ||||||
|   bool hasFocus = false; |  | ||||||
| 
 |  | ||||||
|   @override |  | ||||||
|   void didUpdateWidget(OTPWidget oldWidget) { |  | ||||||
|     super.didUpdateWidget(oldWidget); |  | ||||||
|     focusNode = widget.focusNode ?? focusNode; |  | ||||||
| 
 |  | ||||||
|     if (oldWidget.maxLength < widget.maxLength) { |  | ||||||
|       setState(() { |  | ||||||
|         currentIndex = text.length; |  | ||||||
|       }); |  | ||||||
|       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) { |  | ||||||
|       setState(() { |  | ||||||
|         text = text.substring(0, widget.maxLength); |  | ||||||
|         currentIndex = text.length; |  | ||||||
|       }); |  | ||||||
|       widget.controller?.text = text; |  | ||||||
|       widget.controller?.selection = TextSelection.collapsed(offset: text.length); |  | ||||||
|     } |  | ||||||
|   } |  | ||||||
| 
 |  | ||||||
|   _calculateStrList() { |  | ||||||
|     if (strList.length > widget.maxLength) { |  | ||||||
|       strList.length = widget.maxLength; |  | ||||||
|     } |  | ||||||
|     while (strList.length < widget.maxLength) { |  | ||||||
|       strList.add(""); |  | ||||||
|     } |  | ||||||
|   } |  | ||||||
| 
 |  | ||||||
|   @override |  | ||||||
|   void initState() { |  | ||||||
|     super.initState(); |  | ||||||
|     focusNode = widget.focusNode ?? FocusNode(); |  | ||||||
|     _highlightAnimationController = AnimationController(vsync: this); |  | ||||||
|     _initTextController(); |  | ||||||
|     _calculateStrList(); |  | ||||||
|     widget.controller!.addListener(_controllerListener); |  | ||||||
|     focusNode.addListener(_focusListener); |  | ||||||
|   } |  | ||||||
| 
 |  | ||||||
|   void _controllerListener() { |  | ||||||
|     if (mounted == true) { |  | ||||||
|       setState(() { |  | ||||||
|         _initTextController(); |  | ||||||
|       }); |  | ||||||
|       var onTextChanged = widget.onTextChanged; |  | ||||||
|       if (onTextChanged != null) { |  | ||||||
|         onTextChanged(widget.controller?.text ?? ""); |  | ||||||
|       } |  | ||||||
|     } |  | ||||||
|   } |  | ||||||
| 
 |  | ||||||
|   void _focusListener() { |  | ||||||
|     if (mounted == true) { |  | ||||||
|       setState(() { |  | ||||||
|         hasFocus = focusNode?.hasFocus ?? false; |  | ||||||
|       }); |  | ||||||
|     } |  | ||||||
|   } |  | ||||||
| 
 |  | ||||||
|   void _initTextController() { |  | ||||||
|     if (widget.controller == null) { |  | ||||||
|       return; |  | ||||||
|     } |  | ||||||
|     strList.clear(); |  | ||||||
|     var text = widget.controller?.text ?? ""; |  | ||||||
|     if (text.isNotEmpty) { |  | ||||||
|       if (text.length > widget.maxLength) { |  | ||||||
|         throw Exception("TextEditingController length exceeded maxLength!"); |  | ||||||
|       } |  | ||||||
|     } |  | ||||||
|     for (var i = 0; i < text.length; i++) { |  | ||||||
|       strList.add(text[i]); |  | ||||||
|     } |  | ||||||
|   } |  | ||||||
| 
 |  | ||||||
|   double get _width { |  | ||||||
|     var width = 0.0; |  | ||||||
|     for (var i = 0; i < widget.maxLength; i++) { |  | ||||||
|       width += widget.pinBoxWidth; |  | ||||||
|       if (i == 0) { |  | ||||||
|         width += widget.pinBoxOuterPadding.left; |  | ||||||
|       } else if (i + 1 == widget.maxLength) { |  | ||||||
|         width += widget.pinBoxOuterPadding.right; |  | ||||||
|       } else { |  | ||||||
|         width += widget.pinBoxOuterPadding.left; |  | ||||||
|       } |  | ||||||
|     } |  | ||||||
|     return width; |  | ||||||
|   } |  | ||||||
| 
 |  | ||||||
|   @override |  | ||||||
|   void dispose() { |  | ||||||
|     if (widget.focusNode == null) { |  | ||||||
|       focusNode.dispose(); |  | ||||||
|     } else { |  | ||||||
|       focusNode.removeListener(_focusListener); |  | ||||||
|     } |  | ||||||
|     _highlightAnimationController.dispose(); |  | ||||||
|     widget.controller?.removeListener(_controllerListener); |  | ||||||
| 
 |  | ||||||
|     super.dispose(); |  | ||||||
|   } |  | ||||||
| 
 |  | ||||||
|   @override |  | ||||||
|   Widget build(BuildContext context) { |  | ||||||
|     return Stack( |  | ||||||
|       children: <Widget>[ |  | ||||||
|         _otpTextInput(), |  | ||||||
|         _touchPinBoxRow(), |  | ||||||
|       ], |  | ||||||
|     ); |  | ||||||
|   } |  | ||||||
| 
 |  | ||||||
|   Widget _touchPinBoxRow() { |  | ||||||
|     return widget.hideDefaultKeyboard |  | ||||||
|         ? _pinBoxRow(context) |  | ||||||
|         : GestureDetector( |  | ||||||
|             behavior: HitTestBehavior.opaque, |  | ||||||
|             onTap: () { |  | ||||||
|               if (hasFocus) { |  | ||||||
|                 FocusScope.of(context).requestFocus(FocusNode()); |  | ||||||
|                 Future.delayed(Duration(milliseconds: 100), () { |  | ||||||
|                   FocusScope.of(context).requestFocus(focusNode); |  | ||||||
|                 }); |  | ||||||
|               } else { |  | ||||||
|                 FocusScope.of(context).requestFocus(focusNode); |  | ||||||
|               } |  | ||||||
|             }, |  | ||||||
|             child: _pinBoxRow(context), |  | ||||||
|           ); |  | ||||||
|   } |  | ||||||
| 
 |  | ||||||
|   Widget _otpTextInput() { |  | ||||||
|     var transparentBorder = OutlineInputBorder( |  | ||||||
|       borderSide: BorderSide( |  | ||||||
|         color: Colors.transparent, |  | ||||||
|         width: 0.0, |  | ||||||
|       ), |  | ||||||
|     ); |  | ||||||
|     return Container( |  | ||||||
|       width: _width, |  | ||||||
|       height: widget.pinBoxHeight, |  | ||||||
|       child: TextField( |  | ||||||
|         autofocus: !kIsWeb ? widget.autoFocus : false, |  | ||||||
|         enableInteractiveSelection: false, |  | ||||||
|         focusNode: focusNode, |  | ||||||
|         controller: widget.controller, |  | ||||||
|         keyboardType: widget.keyboardType, |  | ||||||
|         inputFormatters: widget.keyboardType == TextInputType.number ? <TextInputFormatter>[FilteringTextInputFormatter.digitsOnly] : null, |  | ||||||
|         style: TextStyle( |  | ||||||
|           height: 0.1, |  | ||||||
|           color: Colors.transparent, |  | ||||||
|         ), |  | ||||||
|         decoration: InputDecoration( |  | ||||||
|           contentPadding: EdgeInsets.all(0), |  | ||||||
|           focusedErrorBorder: transparentBorder, |  | ||||||
|           errorBorder: transparentBorder, |  | ||||||
|           disabledBorder: transparentBorder, |  | ||||||
|           enabledBorder: transparentBorder, |  | ||||||
|           focusedBorder: transparentBorder, |  | ||||||
|           counterText: null, |  | ||||||
|           counterStyle: null, |  | ||||||
|           helperStyle: TextStyle( |  | ||||||
|             height: 0.0, |  | ||||||
|             color: Colors.transparent, |  | ||||||
|           ), |  | ||||||
|           labelStyle: TextStyle(height: 0.1), |  | ||||||
|           fillColor: Colors.transparent, |  | ||||||
|           border: InputBorder.none, |  | ||||||
|         ), |  | ||||||
|         cursorColor: Colors.transparent, |  | ||||||
|         showCursor: false, |  | ||||||
|         maxLength: widget.maxLength, |  | ||||||
|         onChanged: _onTextChanged, |  | ||||||
|       ), |  | ||||||
|     ); |  | ||||||
|   } |  | ||||||
| 
 |  | ||||||
|   void _onTextChanged(text) { |  | ||||||
|     var onTextChanged = widget.onTextChanged; |  | ||||||
|     if (onTextChanged != null) { |  | ||||||
|       onTextChanged(text); |  | ||||||
|     } |  | ||||||
|     setState(() { |  | ||||||
|       this.text = text; |  | ||||||
|       if (text.length >= currentIndex) { |  | ||||||
|         for (int i = currentIndex; i < text.length; i++) { |  | ||||||
|           strList[i] = text[i]; |  | ||||||
|         } |  | ||||||
|       } |  | ||||||
|       currentIndex = text.length; |  | ||||||
|     }); |  | ||||||
|     if (text.length == widget.maxLength) { |  | ||||||
|       FocusScope.of(context).requestFocus(FocusNode()); |  | ||||||
|       var onDone = widget.onDone; |  | ||||||
|       if (onDone != null) { |  | ||||||
|         onDone(text); |  | ||||||
|       } |  | ||||||
|     } |  | ||||||
|   } |  | ||||||
| 
 |  | ||||||
|   Widget _pinBoxRow(BuildContext context) { |  | ||||||
|     _calculateStrList(); |  | ||||||
|     List<Widget> pinCodes = List.generate(widget.maxLength, (int i) { |  | ||||||
|       return _buildPinCode(i, context); |  | ||||||
|     }); |  | ||||||
|     return Row( |  | ||||||
|       children: pinCodes, |  | ||||||
|       mainAxisSize: MainAxisSize.min, |  | ||||||
|       mainAxisAlignment: MainAxisAlignment.spaceBetween, |  | ||||||
|     ); |  | ||||||
|   } |  | ||||||
| 
 |  | ||||||
|   Widget _buildPinCode(int i, BuildContext context) { |  | ||||||
|     Color borderColor; |  | ||||||
|     Color pinBoxColor = widget.pinBoxColor; |  | ||||||
| 
 |  | ||||||
|     if (widget.hasError) { |  | ||||||
|       borderColor = widget.errorBorderColor; |  | ||||||
|     } else if (i < text.length) { |  | ||||||
|       borderColor = widget.textBorderColor; |  | ||||||
|     } else { |  | ||||||
|       borderColor = widget.defaultBorderColor; |  | ||||||
|       pinBoxColor = widget.pinBoxColor; |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     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 Container( |  | ||||||
|       key: ValueKey<String>("container$i"), |  | ||||||
|       alignment: Alignment.center, |  | ||||||
|       padding: EdgeInsets.symmetric(vertical: 4.0, horizontal: 1.0), |  | ||||||
|       margin: insets, |  | ||||||
|       child: _animatedTextBox(strList[i], i), |  | ||||||
|       decoration: BoxDecoration( |  | ||||||
|         border: Border.all( |  | ||||||
|           color: borderColor, |  | ||||||
|           width: widget.pinBoxBorderWidth, |  | ||||||
|         ), |  | ||||||
|         color: pinBoxColor, |  | ||||||
|         borderRadius: BorderRadius.circular(widget.pinBoxRadius), |  | ||||||
|       ), |  | ||||||
|       width: widget.pinBoxWidth, |  | ||||||
|       height: widget.pinBoxHeight, |  | ||||||
|     ); |  | ||||||
|   } |  | ||||||
| 
 |  | ||||||
|   Widget _animatedTextBox(String text, int i) { |  | ||||||
|     if (widget.pinTextAnimatedSwitcherTransition != null) { |  | ||||||
|       return AnimatedSwitcher( |  | ||||||
|         duration: widget.pinTextAnimatedSwitcherDuration, |  | ||||||
|         transitionBuilder: widget.pinTextAnimatedSwitcherTransition ?? |  | ||||||
|             (Widget child, Animation<double> animation) { |  | ||||||
|               return child; |  | ||||||
|             }, |  | ||||||
|         child: Text( |  | ||||||
|           text, |  | ||||||
|           key: ValueKey<String>("$text$i"), |  | ||||||
|           style: widget.pinTextStyle, |  | ||||||
|         ), |  | ||||||
|       ); |  | ||||||
|     } else { |  | ||||||
|       return Text( |  | ||||||
|         text, |  | ||||||
|         key: ValueKey<String>("${strList[i]}$i"), |  | ||||||
|         style: widget.pinTextStyle, |  | ||||||
|       ); |  | ||||||
|     } |  | ||||||
|   } |  | ||||||
| } |  | ||||||
					Loading…
					
					
				
		Reference in New Issue