OTPTextField
OTPTextField copied to clipboard
IOS Quick Paste
When we clicking on Quick Paste to OTP from IOS Keyboard, It's only pasting the First digit. Can you please fix this.
Thanks for bringing this to my attention. I'll fix this once I get some free time on hand.
Thanks, I hope you do this asap.
is any solution found for quick paste?
Not yet! Seems developer isn't getting anytime free on hand
Yeah, sorry guys. I've implemented a quick fix that still contains some bugs that need to be fixed. You can use the latest Github version of this library in your project.
Add this in your pubsec.yaml
otp_text_field:
git:
url: https://github.com/iamvivekkaushik/OTPTextField.git
any update on this ? @iamvivekkaushik bro ?
any update on this ? @iamvivekkaushik bro ?
It works currently, there are just few bugs (edge cases), that I haven't fixed yet. You can use the latest version of the package.
FYI, it doesn't work correctly. It picks only 1 digit from the keyboard one time code.
Still having this issue. I'm on the current package version (1.1.3)
Try removing ** maxLength: 1,** on line 189 from otp_field.dart file, and it works fine.
@lovepreetSinghElectricpe
This solution did not work for me any new ideas for this issue.
@LobnaShaheen this is my otp_field.dart file
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:ui_common/widgets/otp_text_field_widget/otp_field_style.dart';
enum FieldStyle { underline, box }
class OTPTextField extends StatefulWidget {
/// TextField Controller
final OtpFieldController? controller;
/// Number of the OTP Fields
final int length;
/// Total Width of the OTP Text Field
final double width;
/// Width of the single OTP Field
final double fieldWidth;
/// margin around the text fields
@Deprecated(
"Since there is an issue with the margin because it's around each item, we use [spaceBetween] from now on.")
final EdgeInsetsGeometry? margin;
/// space between the text fields
final double spaceBetween;
/// content padding of the text fields
final EdgeInsets contentPadding;
/// Manage the type of keyboard that shows up
final TextInputType keyboardType;
/// show the error border or not
final bool hasError;
final TextCapitalization textCapitalization;
/// The style to use for the text being edited.
final TextStyle style;
/// The style to use for the text being edited.
final double outlineBorderRadius;
/// Text Field Alignment
/// default: MainAxisAlignment.spaceBetween [MainAxisAlignment]
final MainAxisAlignment textFieldAlignment;
/// Obscure Text if data is sensitive
final bool obscureText;
/// Whether the [InputDecorator.child] is part of a dense form (i.e., uses less vertical
/// space).
final bool isDense;
/// Text Field Style
final OtpFieldStyle? otpFieldStyle;
/// Text Field Style for field shape.
/// default FieldStyle.underline [FieldStyle]
final FieldStyle fieldStyle;
/// Callback function, called when a change is detected to the pin.
final ValueChanged<String>? onChanged;
/// Callback function, called when pin is completed.
final ValueChanged<String>? onCompleted;
final List<TextInputFormatter>? inputFormatter;
const OTPTextField({
Key? key,
this.length = 4,
this.width = 10,
this.controller,
this.fieldWidth = 30,
this.spaceBetween = 0,
this.otpFieldStyle,
this.hasError = false,
this.margin,
this.keyboardType = TextInputType.number,
this.style = const TextStyle(),
this.outlineBorderRadius = 10,
this.textCapitalization = TextCapitalization.none,
this.textFieldAlignment = MainAxisAlignment.spaceBetween,
this.obscureText = false,
this.fieldStyle = FieldStyle.underline,
this.onChanged,
this.inputFormatter,
this.contentPadding =
const EdgeInsets.symmetric(horizontal: 4, vertical: 8),
this.isDense = false,
this.onCompleted,
}) : assert(length > 1),
super(key: key);
@override
_OTPTextFieldState createState() => _OTPTextFieldState();
}
class _OTPTextFieldState extends State<OTPTextField> {
late OtpFieldStyle _otpFieldStyle;
late List<FocusNode?> _focusNodes;
late List<TextEditingController?> _textControllers;
late List<String> _pin;
@override
void initState() {
super.initState();
if (widget.controller != null) {
widget.controller!.setOtpTextFieldState(this);
}
if (widget.otpFieldStyle == null) {
_otpFieldStyle = OtpFieldStyle();
} else {
_otpFieldStyle = widget.otpFieldStyle!;
}
_focusNodes = List<FocusNode?>.filled(widget.length, null, growable: false);
_textControllers = List<TextEditingController?>.filled(widget.length, null,
growable: false);
_pin = List.generate(widget.length, (int i) {
return '';
});
}
@override
void dispose() {
_textControllers.forEach((controller) => controller?.dispose());
super.dispose();
}
@override
Widget build(BuildContext context) {
return SizedBox(
width: widget.width,
child: Row(
mainAxisAlignment: widget.textFieldAlignment,
crossAxisAlignment: CrossAxisAlignment.center,
children: List.generate(widget.length, (index) {
return buildTextField(context, index);
}),
),
);
}
/// This function Build and returns individual TextField item.
///
/// * Requires a build context
/// * Requires Int position of the field
Widget buildTextField(BuildContext context, int index) {
if (_focusNodes[index] == null) _focusNodes[index] = FocusNode();
if (_textControllers[index] == null) {
_textControllers[index] = TextEditingController();
}
final isLast = index == widget.length - 1;
InputBorder _getBorder(Color color) {
final colorOrError =
widget.hasError ? _otpFieldStyle.errorBorderColor : color;
return widget.fieldStyle == FieldStyle.box
? OutlineInputBorder(
borderSide: BorderSide(color: colorOrError, width: 1.5),
borderRadius: BorderRadius.circular(widget.outlineBorderRadius),
)
: UnderlineInputBorder(borderSide: BorderSide(color: colorOrError));
}
return Container(
height: widget.fieldWidth,
width: widget.fieldWidth,
margin: EdgeInsets.only(
right: isLast ? 0 : widget.spaceBetween,
),
child: TextField(
autofillHints: const [AutofillHints.oneTimeCode],
autofocus: true,
controller: _textControllers[index],
keyboardType: widget.keyboardType,
textCapitalization: widget.textCapitalization,
textAlign: TextAlign.center,
style: widget.style,
inputFormatters: widget.inputFormatter,
focusNode: _focusNodes[index],
obscureText: widget.obscureText,
decoration: InputDecoration(
isDense: widget.isDense,
filled: true,
fillColor: _otpFieldStyle.backgroundColor,
counterText: "",
contentPadding: widget.contentPadding,
border: _getBorder(_otpFieldStyle.borderColor),
focusedBorder: _getBorder(_otpFieldStyle.focusBorderColor),
enabledBorder: _getBorder(_otpFieldStyle.enabledBorderColor),
disabledBorder: _getBorder(_otpFieldStyle.disabledBorderColor),
errorBorder: _getBorder(_otpFieldStyle.errorBorderColor),
focusedErrorBorder: _getBorder(_otpFieldStyle.errorBorderColor),
errorText: null,
// to hide the error text
errorStyle: const TextStyle(height: 0, fontSize: 0),
),
onChanged: (String str) {
if (str.length > 1) {
_handlePaste(str);
return;
}
// Check if the current value at this position is empty
// If it is move focus to previous text field.
if (str.isEmpty) {
if (index == 0) return;
_focusNodes[index]!.unfocus();
_focusNodes[index - 1]!.requestFocus();
}
// Update the current pin
setState(() {
_pin[index] = str;
});
// Remove focus
if (str.isNotEmpty) _focusNodes[index]!.unfocus();
// Set focus to the next field if available
if (index + 1 != widget.length && str.isNotEmpty) {
FocusScope.of(context).requestFocus(_focusNodes[index + 1]);
}
String currentPin = _getCurrentPin();
// if there are no null values that means otp is completed
// Call the `onCompleted` callback function provided
if (!_pin.contains(null) &&
!_pin.contains('') &&
currentPin.length == widget.length) {
widget.controller?.pin = currentPin;
widget.onCompleted?.call(currentPin);
}
// Call the `onChanged` callback function
widget.onChanged!(currentPin);
},
),
);
}
String _getCurrentPin() {
String currentPin = "";
_pin.forEach((String value) {
currentPin += value;
});
return currentPin;
}
void _handlePaste(String str) {
if (str.length > widget.length) {
str = str.substring(0, widget.length);
}
for (int i = 0; i < str.length; i++) {
String digit = str.substring(i, i + 1);
_textControllers[i]!.text = digit;
_pin[i] = digit;
}
FocusScope.of(context).requestFocus(_focusNodes[widget.length - 1]);
String currentPin = _getCurrentPin();
// if there are no null values that means otp is completed
// Call the `onCompleted` callback function provided
if (!_pin.contains(null) &&
!_pin.contains('') &&
currentPin.length == widget.length) {
widget.controller?.pin = currentPin;
widget.onCompleted?.call(currentPin);
}
// Call the `onChanged` callback function
widget.onChanged!(currentPin);
}
}
class OtpFieldController {
String pin = "";
late _OTPTextFieldState _otpTextFieldState;
void setOtpTextFieldState(_OTPTextFieldState state) {
_otpTextFieldState = state;
}
void clear() {
final textFieldLength = _otpTextFieldState.widget.length;
_otpTextFieldState._pin = List.generate(textFieldLength, (int i) {
return '';
});
final textControllers = _otpTextFieldState._textControllers;
textControllers.forEach((textController) {
if (textController != null) {
textController.text = '';
}
});
final firstFocusNode = _otpTextFieldState._focusNodes[0];
if (firstFocusNode != null) {
firstFocusNode.requestFocus();
}
}
void set(List<String> pin) {
final textFieldLength = _otpTextFieldState.widget.length;
if (pin.length < textFieldLength) {
throw Exception(
"Pin length must be same as field length. Expected: $textFieldLength, Found ${pin.length}");
}
_otpTextFieldState._pin = pin;
String newPin = '';
final textControllers = _otpTextFieldState._textControllers;
for (int i = 0; i < textControllers.length; i++) {
final textController = textControllers[i];
final pinValue = pin[i];
newPin += pinValue;
if (textController != null) {
textController.text = pinValue;
}
}
final widget = _otpTextFieldState.widget;
widget.onChanged?.call(newPin);
widget.controller?.pin = newPin;
widget.onCompleted?.call(newPin);
}
void setValue(String value, int position) {
final maxIndex = _otpTextFieldState.widget.length - 1;
if (position > maxIndex) {
throw Exception(
"Provided position is out of bounds for the OtpTextField");
}
final textControllers = _otpTextFieldState._textControllers;
final textController = textControllers[position];
final currentPin = _otpTextFieldState._pin;
if (textController != null) {
textController.text = value;
currentPin[position] = value;
}
String newPin = "";
for (var item in currentPin) {
newPin += item;
}
final widget = _otpTextFieldState.widget;
if (widget.onChanged != null) {
widget.onChanged!(newPin);
}
}
void setFocus(int position) {
final maxIndex = _otpTextFieldState.widget.length - 1;
if (position > maxIndex) {
throw Exception(
"Provided position is out of bounds for the OtpTextField");
}
final focusNodes = _otpTextFieldState._focusNodes;
final focusNode = focusNodes[position];
if (focusNode != null) {
focusNode.requestFocus();
}
}
}
and this is how I implemented :
OTPTextField(
keyboardType: TextInputType.number,
controller: _otpController,
length: 4,
width: MediaQuery.of(context).size.width,
textFieldAlignment: MainAxisAlignment.start,
fieldStyle: FieldStyle.box,
otpFieldStyle: OtpFieldStyle(
enabledBorderColor: Colors.black,
focusBorderColor: Colors.black,
borderColor: Colors.black,
fieldWidth: 56,
spaceBetween: 12,
outlineBorderRadius: 8,
style: regularTextStyle(fontSize: 16),
onChanged: (pin) {},
onCompleted: (_) {
//handle logic for pin entered
},
),
Any update on this issue?