otp screen

pull/35/head
aamir-csol 2 months ago
parent f15018b0e8
commit 87830e17ab

@ -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,12 +51,12 @@ class OTPWidget extends StatefulWidget {
final TextInputType keyboardType;
final EdgeInsets pinBoxOuterPadding;
OTPWidget({
const OTPWidget({
Key? key,
this.maxLength = 4,
this.controller,
this.pinBoxWidth = 70.0,
this.pinBoxHeight = 100.0,
this.pinBoxHeight = 70.0,
this.pinTextStyle,
this.onDone,
this.defaultBorderColor = Colors.black,
@ -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();
}
@ -231,7 +236,7 @@ class OTPWidgetState extends State<OTPWidget> with SingleTickerProviderStateMixi
width: 0.0,
),
);
return SizedBox(
return Container(
width: _width,
height: widget.pinBoxHeight,
child: TextField(
@ -241,8 +246,6 @@ 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,
@ -301,33 +304,28 @@ class OTPWidgetState extends State<OTPWidget> with SingleTickerProviderStateMixi
return _buildPinCode(i, context);
});
return Row(
children: pinCodes,
mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: pinCodes,
);
}
Widget _buildPinCode(int i, BuildContext context) {
Color borderColor;
Color pinBoxColor;
// Determine if OTP is complete
bool isComplete = text.length == widget.maxLength;
Color pinBoxColor = widget.pinBoxColor;
if (widget.hasError) {
borderColor = widget.errorBorderColor;
pinBoxColor = widget.pinBoxColor;
} else if (isComplete) {
borderColor = Colors.transparent;
pinBoxColor = AppColors.successColor;
pinBoxColor = widget.errorBorderColor;
} 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;
}
// 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(
@ -346,22 +344,21 @@ 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,
),
child: _animatedTextBox(strList[i], i),
decoration: RoundedRectangleBorder().toSmoothCornerDecoration(
color: pinBoxColor,
borderRadius: BorderRadius.circular(widget.pinBoxRadius),
borderRadius: widget.pinBoxRadius,
),
width: widget.pinBoxWidth,
height: widget.pinBoxHeight,
child: _animatedTextBox(strList[i], i),
);
}
@ -407,10 +404,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;
bool _isOtpComplete = false;
bool _isVerifying = false; // Flag to prevent multiple verification calls
@override
void initState() {
@ -437,27 +436,25 @@ class _OTPVerificationScreenState extends State<OTPVerificationScreen> {
}
void _onOtpChanged(String value) {
// Handle clipboard paste or programmatic input
if (value.length > 1) {
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 = 60;
_isVerifying = false; // Reset verification flag
});
_startResendTimer();
autoFillOtp("1234");
widget.onResendOTPPressed(widget.phoneNumber);
@ -469,68 +466,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(
@ -560,35 +495,30 @@ class _OTPVerificationScreenState extends State<OTPVerificationScreen> {
),
SizedBox(height: 40.h),
// OTP Input Fields using new OTPWidget
// 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(
@ -620,50 +550,15 @@ 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 (otp.length != _otpLength) return;
// Clear any existing text first
_otpController.clear();
// Add a small delay to ensure the UI is updated
Future.delayed(const Duration(milliseconds: 50), () {
_isVerifying = false; // Reset flag before setting new OTP
_otpController.text = otp;
// Move cursor to the end
_otpController.selection = TextSelection.fromPosition(
TextPosition(offset: otp.length),
);
});
}
/// 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,
),
);
}
}
}

Loading…
Cancel
Save