You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
231 lines
7.6 KiB
Dart
231 lines
7.6 KiB
Dart
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/presentation/authentication/register_step2.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')),
|
|
);
|
|
|
|
Navigator.of(context).push(MaterialPageRoute(builder: (BuildContext context) => RegisterNewStep2(null, {"nationalID": "12345678654321"})));
|
|
}
|
|
|
|
/// 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();
|
|
}
|
|
}
|