import 'dart:async'; import 'package:flutter/gestures.dart'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:get/get.dart'; import 'package:taxglide/consts/app_asstes.dart'; import 'package:taxglide/consts/app_colors.dart'; import 'package:taxglide/consts/comman_button.dart'; import 'package:taxglide/consts/comman_container_auth.dart'; import 'package:taxglide/consts/responsive_helper.dart'; import 'package:taxglide/consts/validation_popup.dart'; import 'package:taxglide/controller/api_contoller.dart'; import 'package:taxglide/router/consts_routers.dart'; import 'package:flutter/services.dart'; import 'package:taxglide/consts/app_style.dart'; class EmployeeOtpScreen extends ConsumerStatefulWidget { const EmployeeOtpScreen({super.key}); @override ConsumerState createState() => _EmployeeOtpScreenState(); } class _EmployeeOtpScreenState extends ConsumerState { final ValidationPopup _validationPopup = ValidationPopup(); final int otpLength = 4; final List _controllers = []; final List _focusNodes = []; String otpValue = ''; Timer? _timer; int _remainingSeconds = 600; bool _canResend = false; late String _identifier; bool _fromForgot = false; bool _isEmployee = true; @override void initState() { super.initState(); final args = Get.arguments as Map? ?? {}; _fromForgot = args['fromForgot'] ?? false; _identifier = args['email'] ?? args['mobile'] ?? ''; for (int i = 0; i < otpLength; i++) { _controllers.add(TextEditingController()); _focusNodes.add(FocusNode()); } _startTimer(); } @override void dispose() { _timer?.cancel(); for (var c in _controllers) c.dispose(); for (var f in _focusNodes) f.dispose(); super.dispose(); } void _startTimer() { _canResend = false; _remainingSeconds = 30; _timer?.cancel(); _timer = Timer.periodic(const Duration(seconds: 1), (timer) { if (_remainingSeconds > 0) { setState(() => _remainingSeconds--); } else { setState(() => _canResend = true); timer.cancel(); } }); } String _formatTime(int seconds) { int minutes = seconds ~/ 60; int remainingSeconds = seconds % 60; return '${minutes.toString().padLeft(2, '0')}:${remainingSeconds.toString().padLeft(2, '0')}'; } void _resendOtp() async { if (!_canResend) return; for (var controller in _controllers) controller.clear(); setState(() => otpValue = ''); _focusNodes[0].requestFocus(); if (_fromForgot) { await ref.read(forgotPasswordProvider.notifier).requestOtp(_identifier, _isEmployee); final state = ref.read(forgotPasswordProvider); state.when( data: (result) { if (result['success'] == true) { _validationPopup.showSuccessMessage(context, "OTP has been resent successfully!"); _startTimer(); } else { _validationPopup.showErrorMessage(context, result['error'] ?? "Failed to resend OTP"); } }, loading: () {}, error: (error, _) => _validationPopup.showErrorMessage(context, "Failed to resend OTP"), ); } else { await ref.read(employeeloginProvider.notifier).login(_identifier); final state = ref.read(employeeloginProvider); state.when( data: (result) { if (result['success'] == true) { _validationPopup.showSuccessMessage(context, "OTP has been resent successfully!"); _startTimer(); } else { _validationPopup.showErrorMessage(context, result['error'] ?? "Failed to resend OTP"); } }, loading: () {}, error: (error, _) => _validationPopup.showErrorMessage(context, "Failed to resend OTP"), ); } } void _verifyOtp() async { if (otpValue.length != otpLength) { _validationPopup.showErrorMessage(context, "Please enter all OTP digits"); return; } if (_fromForgot) { await ref.read(forgotPasswordProvider.notifier).verifyOtp(_identifier, otpValue); final state = ref.read(forgotPasswordProvider); state.when( data: (result) { if (result['success'] == true) { _validationPopup.showSuccessMessage(context, "OTP Verified successfully!"); Get.toNamed(ConstRouters.mpinSet); } else { _validationPopup.showErrorMessage(context, result['error'] ?? "Invalid OTP"); } }, loading: () {}, error: (err, _) => _validationPopup.showErrorMessage(context, "Verification failed"), ); } else { await ref.read(employeeloginProvider.notifier).verifyOtp(_identifier, otpValue); final state = ref.read(employeeloginProvider); state.when( data: (result) { if (result['success'] == true) { _validationPopup.showSuccessMessage(context, "OTP Verified successfully!"); Get.offAllNamed(ConstRouters.mpinSet); } else { _validationPopup.showErrorMessage(context, result['error'] ?? "Invalid OTP"); } }, loading: () {}, error: (err, _) => _validationPopup.showErrorMessage(context, "Verification failed"), ); } } void _onOtpChange(int index, String value) { if (value.isNotEmpty && index < otpLength - 1) { _focusNodes[index + 1].requestFocus(); } if (value.isEmpty && index > 0) { _focusNodes[index - 1].requestFocus(); } setState(() { otpValue = _controllers.map((c) => c.text).join(); }); } @override Widget build(BuildContext context) { final isLoading = _fromForgot ? ref.watch(forgotPasswordProvider).isLoading : ref.watch(employeeloginProvider).isLoading; final r = ResponsiveUtils(context); final logoWidth = r.getValue(mobile: 120, tablet: 141, desktop: 160); final logoHeight = r.getValue(mobile: 85, tablet: 100, desktop: 115); final titleFontSize = r.fontSize(mobile: 26, tablet: 32, desktop: 36); final subtitleFontSize = r.fontSize(mobile: 13, tablet: 14, desktop: 15); final resendFontSize = r.fontSize(mobile: 13, tablet: 14, desktop: 15); final timerFontSize = r.fontSize(mobile: 14, tablet: 16, desktop: 18); final otpBoxWidth = r.getValue(mobile: 52, tablet: 60, desktop: 68); final otpBoxHeight = r.getValue(mobile: 48, tablet: 56, desktop: 64); final otpFontSize = r.fontSize(mobile: 22, tablet: 28, desktop: 32); final otpBoxMargin = r.getValue(mobile: 5, tablet: 6, desktop: 8); final otpBoxRadius = r.getValue(mobile: 5, tablet: 6, desktop: 8); final spacingSM = r.spacing(mobile: 20, tablet: 20, desktop: 24); final spacingMD = r.spacing(mobile: 22, tablet: 22, desktop: 26); final spacingLG = r.spacing(mobile: 30, tablet: 30, desktop: 36); return Scaffold( body: Stack( children: [ Container( decoration: const BoxDecoration( gradient: LinearGradient( begin: Alignment.topLeft, end: Alignment.bottomRight, colors: [ Color(0xFFFFF8F0), Color(0xFFEBC894), Color(0xFFE8DAF2), Color(0xFFB49EF4), ], stops: [0.0, 0.3, 0.6, 1.0], ), ), ), SingleChildScrollView( child: SizedBox( height: r.screenHeight, child: Center( child: CommonContainerAuth( child: Column( mainAxisSize: MainAxisSize.min, children: [ Image.asset( AppAssets.taxgildelogoauth, width: logoWidth, height: logoHeight, fit: BoxFit.contain, ), SizedBox(height: spacingSM), Text( "Enter Staff OTP", style: AppTextStyles.bold.copyWith( fontSize: titleFontSize, color: AppColors.authheading, ), ), SizedBox(height: spacingSM), Text( "OTP has been sent to ${_identifier}", textAlign: TextAlign.center, style: AppTextStyles.medium.copyWith( fontSize: subtitleFontSize, color: AppColors.authleading, ), ), SizedBox(height: spacingMD), Row( mainAxisAlignment: MainAxisAlignment.center, children: List.generate(otpLength, (index) { bool isFilled = _controllers[index].text.isNotEmpty; return Container( margin: EdgeInsets.symmetric(horizontal: otpBoxMargin), width: otpBoxWidth, height: otpBoxHeight, decoration: BoxDecoration( color: isFilled ? AppColors.commanbutton : Colors.white, borderRadius: BorderRadius.circular(otpBoxRadius), border: Border.all(color: const Color(0xFFDFDFDF)), boxShadow: [ BoxShadow( color: const Color(0xFFBDBDBD).withOpacity(0.25), blurRadius: 7, offset: const Offset(0, 1), ), ], ), child: Center( child: KeyboardListener( focusNode: FocusNode(), onKeyEvent: (event) { if (event is KeyDownEvent && event.logicalKey == LogicalKeyboardKey.backspace) { if (_controllers[index].text.isEmpty && index > 0) { _focusNodes[index - 1].requestFocus(); } } }, child: TextField( controller: _controllers[index], focusNode: _focusNodes[index], textAlign: TextAlign.center, keyboardType: TextInputType.number, maxLength: 1, enabled: !isLoading, style: TextStyle( fontFamily: "Gilroy", fontWeight: FontWeight.w400, fontSize: otpFontSize, color: isFilled ? Colors.white : Colors.black, ), decoration: const InputDecoration( counterText: '', border: InputBorder.none, ), onChanged: (value) => _onOtpChange(index, value), ), ), ), ); }), ), SizedBox(height: spacingSM), Align( alignment: Alignment.centerRight, child: GestureDetector( onTap: _canResend && !isLoading ? _resendOtp : null, child: Text( "Resend", style: AppTextStyles.medium.copyWith( fontSize: resendFontSize, color: _canResend && !isLoading ? AppColors.authchange : Colors.grey, ), ), ), ), SizedBox(height: spacingSM), Text( _formatTime(_remainingSeconds), style: AppTextStyles.semiBold.copyWith( fontSize: timerFontSize, color: _remainingSeconds > 0 ? AppColors.authheading : Colors.red, ), ), SizedBox(height: spacingLG), isLoading ? const CircularProgressIndicator() : CommanButton(text: "Verify", onPressed: _verifyOtp), ], ), ), ), ), ), ], ), ); } }