changes
parent
8f1e4500cc
commit
67e580447e
@ -0,0 +1,227 @@
|
||||
import 'dart:async';
|
||||
import 'package:flutter/material.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';
|
||||
|
||||
class OTPVerificationPage extends StatefulWidget {
|
||||
final String phoneNumber;
|
||||
|
||||
const OTPVerificationPage({Key? key, required this.phoneNumber}) : super(key: key);
|
||||
|
||||
@override
|
||||
State<OTPVerificationPage> createState() => _OTPVerificationPageState();
|
||||
}
|
||||
|
||||
class _OTPVerificationPageState extends State<OTPVerificationPage> {
|
||||
final int _otpLength = 4;
|
||||
late final List<TextEditingController> _controllers;
|
||||
late final List<FocusNode> _focusNodes;
|
||||
|
||||
Timer? _resendTimer;
|
||||
int _resendTime = 60;
|
||||
bool _isOtpComplete = false;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_controllers = List.generate(_otpLength, (_) => TextEditingController());
|
||||
_focusNodes = List.generate(_otpLength, (_) => FocusNode());
|
||||
_startResendTimer();
|
||||
|
||||
// Focus the first field once the screen is built
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
if (_focusNodes.isNotEmpty) {
|
||||
FocusScope.of(context).requestFocus(_focusNodes[0]);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
for (final c in _controllers) c.dispose();
|
||||
for (final f in _focusNodes) f.dispose();
|
||||
_resendTimer?.cancel();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
void _startResendTimer() {
|
||||
_resendTimer = Timer.periodic(const Duration(seconds: 1), (timer) {
|
||||
if (_resendTime > 0) {
|
||||
setState(() => _resendTime--);
|
||||
} else {
|
||||
timer.cancel();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void _onOtpChanged(int index, String value) {
|
||||
if (value.length == 1 && index < _otpLength - 1) {
|
||||
_focusNodes[index + 1].requestFocus();
|
||||
} else if (value.isEmpty && index > 0) {
|
||||
_focusNodes[index - 1].requestFocus();
|
||||
}
|
||||
_checkOtpCompletion();
|
||||
}
|
||||
|
||||
void _checkOtpCompletion() {
|
||||
final isComplete = _controllers.every((c) => c.text.isNotEmpty);
|
||||
|
||||
if (isComplete != _isOtpComplete) {
|
||||
setState(() => _isOtpComplete = isComplete);
|
||||
|
||||
if (isComplete) {
|
||||
_verifyOtp();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void _resendOtp() {
|
||||
if (_resendTime == 0) {
|
||||
setState(() => _resendTime = 60);
|
||||
_startResendTimer();
|
||||
autoFillOtp("1234");
|
||||
|
||||
// call resend API here
|
||||
}
|
||||
}
|
||||
|
||||
String _getMaskedPhoneNumber() {
|
||||
final phone = widget.phoneNumber;
|
||||
return phone.length > 4 ? '05xxxxxx${phone.substring(phone.length - 2)}' : phone;
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: CustomAppBar(
|
||||
hideLogoAndLang: true,
|
||||
onBackPressed: () {
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
onLanguageChanged: (lang) {},
|
||||
),
|
||||
body: SingleChildScrollView(
|
||||
child: Padding(
|
||||
padding: EdgeInsets.symmetric(horizontal: 24.h),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
SizedBox(height: 40.h),
|
||||
Text(
|
||||
'OTP Verification',
|
||||
style: TextStyle(fontSize: 24.fSize, fontWeight: FontWeight.bold),
|
||||
),
|
||||
SizedBox(height: 16.h),
|
||||
Text(
|
||||
'We have sent you the OTP code on ${_getMaskedPhoneNumber()} via SMS for registration verification',
|
||||
style: TextStyle(fontSize: 16.fSize, color: Colors.grey),
|
||||
),
|
||||
SizedBox(height: 40.h),
|
||||
|
||||
// OTP Input Fields
|
||||
SizedBox(
|
||||
height: 100,
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
|
||||
children: List.generate(_otpLength, (index) {
|
||||
return ValueListenableBuilder<TextEditingValue>(
|
||||
valueListenable: _controllers[index],
|
||||
builder: (context, value, _) {
|
||||
final hasText = value.text.isNotEmpty;
|
||||
|
||||
return AnimatedContainer(
|
||||
duration: const Duration(milliseconds: 200),
|
||||
curve: Curves.easeInOut,
|
||||
width: 70.h,
|
||||
margin: EdgeInsets.symmetric(horizontal: 4.h),
|
||||
decoration: RoundedRectangleBorder()
|
||||
.toSmoothCornerDecoration(color: _isOtpComplete ? AppColors.successColor : (hasText ? AppColors.blackBgColor : AppColors.whiteColor), borderRadius: 16),
|
||||
child: Center(
|
||||
child: TextField(
|
||||
controller: _controllers[index],
|
||||
focusNode: _focusNodes[index],
|
||||
textAlign: TextAlign.center,
|
||||
keyboardType: TextInputType.number,
|
||||
maxLength: 1,
|
||||
style: TextStyle(
|
||||
fontSize: 40.fSize,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: AppColors.whiteColor,
|
||||
),
|
||||
decoration: InputDecoration(
|
||||
counterText: '',
|
||||
filled: true,
|
||||
fillColor: Colors.transparent,
|
||||
border: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(18),
|
||||
borderSide: BorderSide.none,
|
||||
),
|
||||
),
|
||||
onChanged: (v) => _onOtpChanged(index, v),
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}),
|
||||
),
|
||||
),
|
||||
|
||||
const SizedBox(height: 32),
|
||||
|
||||
// Resend OTP
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
const Text("Didn't receive it? "),
|
||||
if (_resendTime > 0)
|
||||
Text(
|
||||
'resend in (${_resendTime.toString().padLeft(2, '0')}:00). ',
|
||||
style: const TextStyle(color: Colors.grey),
|
||||
)
|
||||
else
|
||||
GestureDetector(
|
||||
onTap: _resendOtp,
|
||||
child: const Text(
|
||||
'Resend',
|
||||
style: TextStyle(
|
||||
color: AppColors.primaryRedColor,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
void _verifyOtp() {
|
||||
final otp = _controllers.map((c) => c.text).join();
|
||||
debugPrint('Verifying OTP: $otp');
|
||||
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(content: Text('Verifying OTP: $otp')),
|
||||
);
|
||||
}
|
||||
|
||||
/// Auto fill OTP into text fields
|
||||
void autoFillOtp(String otp) {
|
||||
if (otp.length != _otpLength) return;
|
||||
|
||||
for (int i = 0; i < _otpLength; i++) {
|
||||
_controllers[i].text = otp[i];
|
||||
}
|
||||
|
||||
// Move focus to the last field
|
||||
_focusNodes[_otpLength - 1].requestFocus();
|
||||
|
||||
// Trigger completion check and color update
|
||||
_checkOtpCompletion();
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue