added otp resend cooldown
This commit is contained in:
@@ -1,3 +1,5 @@
|
|||||||
|
import 'dart:async';
|
||||||
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:go_router/go_router.dart';
|
import 'package:go_router/go_router.dart';
|
||||||
import 'package:qpay/screens/onboarding/verify/verify_controller.dart';
|
import 'package:qpay/screens/onboarding/verify/verify_controller.dart';
|
||||||
@@ -14,11 +16,19 @@ class VerifyScreen extends StatefulWidget {
|
|||||||
class _VerifyScreenState extends State<VerifyScreen> {
|
class _VerifyScreenState extends State<VerifyScreen> {
|
||||||
late VerifyController verifyController;
|
late VerifyController verifyController;
|
||||||
String? errorMessage;
|
String? errorMessage;
|
||||||
|
// Resend cooldown
|
||||||
|
final Duration _resendCooldown = const Duration(minutes: 1);
|
||||||
|
DateTime? _resendAvailableAt;
|
||||||
|
Timer? _resendTimer;
|
||||||
|
int _remainingSeconds = 0;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
verifyController = VerifyController(context);
|
verifyController = VerifyController(context);
|
||||||
|
// Start the resend cooldown immediately since the OTP was just sent
|
||||||
|
// during registration.
|
||||||
|
_startResendCooldown();
|
||||||
}
|
}
|
||||||
|
|
||||||
final List<TextEditingController> _codeControllers = List.generate(
|
final List<TextEditingController> _codeControllers = List.generate(
|
||||||
@@ -35,9 +45,43 @@ class _VerifyScreenState extends State<VerifyScreen> {
|
|||||||
for (var node in _focusNodes) {
|
for (var node in _focusNodes) {
|
||||||
node.dispose();
|
node.dispose();
|
||||||
}
|
}
|
||||||
|
_resendTimer?.cancel();
|
||||||
super.dispose();
|
super.dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void _startResendCooldown() {
|
||||||
|
_resendTimer?.cancel();
|
||||||
|
_resendAvailableAt = DateTime.now().add(_resendCooldown);
|
||||||
|
_remainingSeconds = _resendCooldown.inSeconds;
|
||||||
|
if (mounted) setState(() {});
|
||||||
|
_resendTimer = Timer.periodic(
|
||||||
|
const Duration(seconds: 1),
|
||||||
|
(_) => _updateRemaining(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
void _updateRemaining() {
|
||||||
|
if (_resendAvailableAt == null) return;
|
||||||
|
final seconds = _resendAvailableAt!.difference(DateTime.now()).inSeconds;
|
||||||
|
if (seconds <= 0) {
|
||||||
|
_resendTimer?.cancel();
|
||||||
|
setState(() {
|
||||||
|
_resendAvailableAt = null;
|
||||||
|
_remainingSeconds = 0;
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
setState(() {
|
||||||
|
_remainingSeconds = seconds;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
String _formatDuration(int totalSeconds) {
|
||||||
|
final mins = (totalSeconds ~/ 60).toString().padLeft(2, '0');
|
||||||
|
final secs = (totalSeconds % 60).toString().padLeft(2, '0');
|
||||||
|
return '$mins:$secs';
|
||||||
|
}
|
||||||
|
|
||||||
void showErrorMessage(String? message) {
|
void showErrorMessage(String? message) {
|
||||||
setState(() {
|
setState(() {
|
||||||
errorMessage = message;
|
errorMessage = message;
|
||||||
@@ -172,10 +216,7 @@ class _VerifyScreenState extends State<VerifyScreen> {
|
|||||||
),
|
),
|
||||||
focusedBorder: OutlineInputBorder(
|
focusedBorder: OutlineInputBorder(
|
||||||
borderRadius: borderRadius,
|
borderRadius: borderRadius,
|
||||||
borderSide: BorderSide(
|
borderSide: BorderSide(color: colorScheme.primary, width: 1.6),
|
||||||
color: colorScheme.primary,
|
|
||||||
width: 1.6,
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
errorBorder: OutlineInputBorder(
|
errorBorder: OutlineInputBorder(
|
||||||
borderRadius: borderRadius,
|
borderRadius: borderRadius,
|
||||||
@@ -183,10 +224,7 @@ class _VerifyScreenState extends State<VerifyScreen> {
|
|||||||
),
|
),
|
||||||
focusedErrorBorder: OutlineInputBorder(
|
focusedErrorBorder: OutlineInputBorder(
|
||||||
borderRadius: borderRadius,
|
borderRadius: borderRadius,
|
||||||
borderSide: BorderSide(
|
borderSide: BorderSide(color: colorScheme.error, width: 1.6),
|
||||||
color: colorScheme.error,
|
|
||||||
width: 1.6,
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
onChanged: (value) => _onCodeChanged(value, index),
|
onChanged: (value) => _onCodeChanged(value, index),
|
||||||
@@ -209,9 +247,27 @@ class _VerifyScreenState extends State<VerifyScreen> {
|
|||||||
fontSize: 14,
|
fontSize: 14,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
TextButton(
|
// Show countdown while cooling down, otherwise show resend button
|
||||||
onPressed: () {
|
_remainingSeconds == 0
|
||||||
verifyController.resendOtp();
|
? TextButton(
|
||||||
|
onPressed: verifyController.model.isLoading
|
||||||
|
? null
|
||||||
|
: () async {
|
||||||
|
// Start cooldown immediately to give immediate feedback
|
||||||
|
_startResendCooldown();
|
||||||
|
final resp = await verifyController.resendOtp();
|
||||||
|
if (!mounted) return;
|
||||||
|
if (!resp.isSuccess) {
|
||||||
|
// cancel cooldown on failure and show error
|
||||||
|
_resendTimer?.cancel();
|
||||||
|
setState(() {
|
||||||
|
_resendAvailableAt = null;
|
||||||
|
_remainingSeconds = 0;
|
||||||
|
});
|
||||||
|
showErrorMessage(
|
||||||
|
resp.error ?? 'Failed to resend code',
|
||||||
|
);
|
||||||
|
}
|
||||||
},
|
},
|
||||||
style: TextButton.styleFrom(
|
style: TextButton.styleFrom(
|
||||||
foregroundColor: colorScheme.primary,
|
foregroundColor: colorScheme.primary,
|
||||||
@@ -222,6 +278,17 @@ class _VerifyScreenState extends State<VerifyScreen> {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
child: const Text('Resend code'),
|
child: const Text('Resend code'),
|
||||||
|
)
|
||||||
|
: Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 6),
|
||||||
|
child: Text(
|
||||||
|
'Resend in ${_formatDuration(_remainingSeconds)}',
|
||||||
|
style: TextStyle(
|
||||||
|
color: colorScheme.onSurface.withValues(alpha: 0.65),
|
||||||
|
fontSize: 14,
|
||||||
|
fontWeight: FontWeight.w700,
|
||||||
|
),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
|||||||
Reference in New Issue
Block a user