Merge pull request 'validations & otp widget & focus & auto fill' (#32) from dev_aamir into master
Reviewed-on: #32pull/35/head
						commit
						f15018b0e8
					
				| @ -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