From 202756a6bbe37c7f506aa8bbe355a368ed1c19a0 Mon Sep 17 00:00:00 2001 From: Prince Date: Fri, 12 Jun 2026 11:56:00 +0200 Subject: [PATCH] added otp resend cooldown --- .../onboarding/verify/verify_screen.dart | 111 ++++++++++++++---- 1 file changed, 89 insertions(+), 22 deletions(-) diff --git a/lib/screens/onboarding/verify/verify_screen.dart b/lib/screens/onboarding/verify/verify_screen.dart index 7952d6d..7f946a7 100644 --- a/lib/screens/onboarding/verify/verify_screen.dart +++ b/lib/screens/onboarding/verify/verify_screen.dart @@ -1,3 +1,5 @@ +import 'dart:async'; + import 'package:flutter/material.dart'; import 'package:go_router/go_router.dart'; import 'package:qpay/screens/onboarding/verify/verify_controller.dart'; @@ -14,11 +16,19 @@ class VerifyScreen extends StatefulWidget { class _VerifyScreenState extends State { late VerifyController verifyController; String? errorMessage; + // Resend cooldown + final Duration _resendCooldown = const Duration(minutes: 1); + DateTime? _resendAvailableAt; + Timer? _resendTimer; + int _remainingSeconds = 0; @override void initState() { super.initState(); verifyController = VerifyController(context); + // Start the resend cooldown immediately since the OTP was just sent + // during registration. + _startResendCooldown(); } final List _codeControllers = List.generate( @@ -35,9 +45,43 @@ class _VerifyScreenState extends State { for (var node in _focusNodes) { node.dispose(); } + _resendTimer?.cancel(); 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) { setState(() { errorMessage = message; @@ -172,10 +216,7 @@ class _VerifyScreenState extends State { ), focusedBorder: OutlineInputBorder( borderRadius: borderRadius, - borderSide: BorderSide( - color: colorScheme.primary, - width: 1.6, - ), + borderSide: BorderSide(color: colorScheme.primary, width: 1.6), ), errorBorder: OutlineInputBorder( borderRadius: borderRadius, @@ -183,10 +224,7 @@ class _VerifyScreenState extends State { ), focusedErrorBorder: OutlineInputBorder( borderRadius: borderRadius, - borderSide: BorderSide( - color: colorScheme.error, - width: 1.6, - ), + borderSide: BorderSide(color: colorScheme.error, width: 1.6), ), ), onChanged: (value) => _onCodeChanged(value, index), @@ -209,20 +247,49 @@ class _VerifyScreenState extends State { fontSize: 14, ), ), - TextButton( - onPressed: () { - verifyController.resendOtp(); - }, - style: TextButton.styleFrom( - foregroundColor: colorScheme.primary, - padding: const EdgeInsets.symmetric(horizontal: 6), - textStyle: const TextStyle( - fontSize: 14, - fontWeight: FontWeight.w700, - ), - ), - child: const Text('Resend code'), - ), + // Show countdown while cooling down, otherwise show resend button + _remainingSeconds == 0 + ? 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( + foregroundColor: colorScheme.primary, + padding: const EdgeInsets.symmetric(horizontal: 6), + textStyle: const TextStyle( + fontSize: 14, + fontWeight: FontWeight.w700, + ), + ), + 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, + ), + ), + ), ], ), );