import 'package:flutter/material.dart'; import 'package:flutter_svg/flutter_svg.dart'; import 'package:gen_rentals/Screens/DashboardScreen.dart'; import 'package:provider/provider.dart'; import '../../Notifier/RentalContactProvider .dart'; import '../../Utility/AdvancedSnackbar.dart'; import '../../Utility/CustomSnackbar.dart'; import '../../Utility/SharedpreferencesService.dart'; class OtpScreen extends StatefulWidget { final String mob; // phone number const OtpScreen({ super.key, required this.mob, }); @override State createState() => _OtpScreenState(); } class _OtpScreenState extends State { final prefs = SharedPreferencesService.instance; final List _controllers = List.generate(4, (_) => TextEditingController()); final List _focusNodes = List.generate(4, (_) => FocusNode()); bool _isValid = false; bool _isVerifying = false; bool _isResending = false; int _secondsRemaining = 22; @override void initState() { super.initState(); _startTimer(); } @override void dispose() { for (var controller in _controllers) { controller.dispose(); } for (var focusNode in _focusNodes) { focusNode.dispose(); } super.dispose(); } void _startTimer() { Future.doWhile(() async { if (_secondsRemaining > 0) { await Future.delayed(const Duration(seconds: 1)); if (mounted) { setState(() => _secondsRemaining--); } return true; } return false; }); } void _onOtpChange() { final otp = _controllers.map((c) => c.text).join(); setState(() { _isValid = otp.length == 4 && RegExp(r'^[0-9]{4}$').hasMatch(otp); }); } Future _verifyOtp() async { final rentalProvider = Provider.of(context, listen: false); final enteredOtp = _controllers.map((c) => c.text).join(); setState(() => _isVerifying = true); try { // Remove .toString() since enteredOtp is already a string await rentalProvider.fetchMobileOtp(widget.mob, enteredOtp); if (rentalProvider.otpResponse != null) { if (rentalProvider.otpResponse?.error == "0") { // ✅ OTP verified successfully if (!mounted) return; AnimatedSnackBar.success( context: context, title: "Success", message: rentalProvider.otpResponse?.message ?? "OTP Verified Successfully!", ); // Saving staff id after verification // Added null check for accId if (rentalProvider.otpResponse?.accId != null) { await prefs.saveString("accId", rentalProvider.otpResponse!.accId!); await prefs.saveString("session_id", rentalProvider.otpResponse!.sessionId!); } // Navigate to dashboard Navigator.pushReplacement( context, MaterialPageRoute(builder: (_) => DashboardScreen( accId: rentalProvider.otpResponse!.accId!, sessionId: rentalProvider.otpResponse!.sessionId!,) ), ); } else { // ❌ Invalid OTP CustomSnackBar.showError( context: context, title: "Error", message: rentalProvider.otpResponse?.errorMsg ?? rentalProvider.otpResponse?.message ?? "Invalid OTP. Please try again.", ); } } else { // ❌ API Error - Response is null CustomSnackBar.showError( context: context, title: "Error", message: "Failed to verify OTP. Please try again.", ); } } catch (e) { // ❌ Network Error CustomSnackBar.showError( context: context, title: "Network Error", message: e.toString(), ); } finally { if (mounted) setState(() => _isVerifying = false); } } /// 🔁 Resend OTP Function Future _resendOtp(BuildContext context) async { setState(() => _isResending = true); try { final rentalProvider = Provider.of(context, listen: false); // Call the mobile API again to get new OTP await rentalProvider.fetchRentalMobile(widget.mob); if (rentalProvider.response != null && rentalProvider.response?.error == "0") { // ✅ Successfully resent OTP AnimatedSnackBar.success( context: context, title: "OTP Sent", message: rentalProvider.response?.message ?? "OTP has been sent to your mobile number.", ); // Reset timer and clear fields setState(() { _secondsRemaining = 22; }); // Clear previous OTP fields for (var controller in _controllers) { controller.clear(); } setState(() { _isValid = false; }); // Restart timer _startTimer(); } else { // ❌ Failed to resend OTP CustomSnackBar.showWarning( context: context, title: "Error", message: rentalProvider.response?.errorMsg ?? rentalProvider.response?.message ?? "Failed to resend OTP", ); } } catch (e) { CustomSnackBar.showError( context: context, title: "Network Error", message: e.toString(), ); } finally { if (mounted) setState(() => _isResending = false); } } Widget _buildOtpField(int index) { return SizedBox( width: 60, height: 60, child: TextField( controller: _controllers[index], focusNode: _focusNodes[index], keyboardType: TextInputType.number, textAlign: TextAlign.center, maxLength: 1, style: const TextStyle( fontSize: 24, color: Colors.black, fontWeight: FontWeight.w600, ), decoration: InputDecoration( counterText: "", filled: true, fillColor: Colors.white, enabledBorder: OutlineInputBorder( borderRadius: BorderRadius.circular(60), borderSide: BorderSide(color: Colors.grey.withOpacity(0.5), width: 1), ), focusedBorder: OutlineInputBorder( borderRadius: BorderRadius.circular(60), borderSide: const BorderSide(color: Color(0xFF008CDE), width: 2), ), ), onChanged: (value) { _onOtpChange(); if (value.isNotEmpty && index < 3) { FocusScope.of(context).requestFocus(_focusNodes[index + 1]); } else if (value.isEmpty && index > 0) { FocusScope.of(context).requestFocus(_focusNodes[index - 1]); } }, ), ); } @override Widget build(BuildContext context) { return Scaffold( resizeToAvoidBottomInset: true, body: GestureDetector( onTap: () => FocusScope.of(context).unfocus(), child: Stack( children: [ // Background image Positioned.fill( child: Image.asset( 'assets/images/background.jpg', fit: BoxFit.cover, ), ), // Overlay Positioned.fill( child: Container(color: Colors.black.withOpacity(0.5)), ), // Gradient overlay Positioned.fill( child: Container( decoration: const BoxDecoration( gradient: LinearGradient( begin: Alignment.topCenter, end: Alignment.bottomCenter, colors: [Colors.transparent, Colors.black54, Colors.black87], ), ), ), ), // Foreground content SingleChildScrollView( padding: EdgeInsets.only( bottom: MediaQuery.of(context).viewInsets.bottom + 20, ), child: ConstrainedBox( constraints: BoxConstraints(minHeight: MediaQuery.of(context).size.height), child: Column( mainAxisAlignment: MainAxisAlignment.end, children: [ const SizedBox(height: 40), const Padding( padding: EdgeInsets.symmetric(horizontal: 24), child: Text( "Rental Power,\nManaged in a Tap", textAlign: TextAlign.center, style: TextStyle( color: Colors.white, fontSize: 32, fontWeight: FontWeight.w500, height: 1.3, ), ), ), const SizedBox(height: 40), Padding( padding: const EdgeInsets.symmetric(horizontal: 24), child: Text( "Enter the OTP sent to +91 ${widget.mob}", textAlign: TextAlign.center, style: const TextStyle( color: Colors.white70, fontSize: 14, ), ), ), const SizedBox(height: 30), Padding( padding: const EdgeInsets.symmetric(horizontal: 24), child: Row( mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: List.generate(4, _buildOtpField), ), ), const SizedBox(height: 20), // Resend OTP TextButton( onPressed: _secondsRemaining == 0 && !_isResending ? () => _resendOtp(context) : null, child: _isResending ? const SizedBox( width: 20, height: 20, child: CircularProgressIndicator( strokeWidth: 2, color: Colors.white, ), ) : Text( _secondsRemaining == 0 ? "Resend OTP" : "Resend OTP in $_secondsRemaining s", style: TextStyle( color: _secondsRemaining == 0 ? const Color(0xFF008CDE) : Colors.white70, fontSize: 14, decoration: _secondsRemaining == 0 ? TextDecoration.underline : TextDecoration.none, ), ), ), const SizedBox(height: 30), // Continue Button Padding( padding: const EdgeInsets.symmetric(horizontal: 24), child: SizedBox( width: double.infinity, child: ElevatedButton( onPressed: _isValid && !_isVerifying ? _verifyOtp : null, style: ElevatedButton.styleFrom( backgroundColor: _isValid ? const Color(0xFF008CDE) : const Color(0xFF266E99), foregroundColor: Colors.white, disabledBackgroundColor: const Color(0xFF266E99), shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(30), ), padding: const EdgeInsets.symmetric(vertical: 16), ), child: _isVerifying ? const SizedBox( width: 22, height: 22, child: CircularProgressIndicator( strokeWidth: 2, color: Colors.white, ), ) : Padding( padding: const EdgeInsets.symmetric(horizontal: 22), child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text( "Continue", style: TextStyle( color: _isValid ? Colors.white : const Color(0xFF03456C), fontSize: 16, ), ), SvgPicture.asset( "assets/svg/continue_ic.svg", color: _isValid ? Colors.white : const Color(0xFF03456C), height: 25, width: 25, ), ], ), ), ), ), ), const SizedBox(height: 40), ], ), ), ), ], ), ), ); } }