import 'dart:async'; import 'package:flutter/material.dart'; import 'package:flutter_svg/flutter_svg.dart'; import 'package:gen_service/Notifiers/AuthProvider.dart'; import 'package:provider/provider.dart'; import '../../Utility/AdvancedSnackbar.dart'; import '../../Utility/CustomSnackbar.dart'; import '../../Utility/SharedpreferencesService.dart'; import '../HomeScreen.dart'; class OtpScreen extends StatefulWidget { final String mob; const OtpScreen({super.key, required this.mob}); @override State createState() => _OtpScreenState(); } class _OtpScreenState extends State { final List _controllers = List.generate(4, (_) => TextEditingController()); final List _focusNodes = List.generate(4, (_) => FocusNode()); final prefs = SharedPreferencesService.instance; bool _isValid = false; bool _isVerifying = false; bool _isResending = false; int _secondsRemaining = 22; Timer? _timer; @override void initState() { super.initState(); _startTimer(); } void _startTimer() { _timer?.cancel(); _secondsRemaining = 22; _timer = Timer.periodic(const Duration(seconds: 1), (timer) { if (_secondsRemaining > 0) { setState(() => _secondsRemaining--); } else { timer.cancel(); } }); } 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 authProvider = Provider.of(context, listen: false); final enteredOtp = _controllers.map((c) => c.text).join(); if (enteredOtp.length != 4) { CustomSnackBar.showWarning( context: context, title: "Incomplete OTP", message: "Please enter all 4 digits of the OTP.", ); return; } setState(() => _isVerifying = true); try { await authProvider.verifyOtp(widget.mob, enteredOtp); if (authProvider.otpResponse != null) { if (authProvider.otpResponse?.error == "0") { if (!mounted) return; AnimatedSnackBar.success( context: context, title: "Success", message: authProvider.otpResponse?.message ?? "OTP Verified Successfully!", ); // ✅ Save session and accId if (authProvider.otpResponse?.accId != null) { await prefs.saveString("accId", authProvider.otpResponse!.accId!); await prefs.saveString("session_id", authProvider.otpResponse!.sessionId!); } // ✅ Navigate to Home Screen Navigator.pushReplacement( context, PageRouteBuilder( pageBuilder: (_, __, ___) => HomeScreen( accId: authProvider.otpResponse!.accId!, sessionId: authProvider.otpResponse!.sessionId.toString(), ), transitionsBuilder: (_, animation, __, child) { return FadeTransition(opacity: animation, child: child); }, transitionDuration: const Duration(milliseconds: 800), ), ); } else { CustomSnackBar.showError( context: context, title: "Error", message: authProvider.otpResponse?.message ?? "Invalid OTP. Please try again.", ); } } else { CustomSnackBar.showError( context: context, title: "Error", message: "Failed to verify OTP. Please try again.", ); } } catch (e) { CustomSnackBar.showError( context: context, title: "Network Error", message: e.toString(), ); } finally { if (mounted) setState(() => _isVerifying = false); } } Future _resendOtp(BuildContext context) async { setState(() => _isResending = true); try { final authProvider = Provider.of(context, listen: false); await authProvider.fetchMobile(widget.mob); if (authProvider.mobileResponse != null && authProvider.mobileResponse?.error == "0") { AnimatedSnackBar.success( context: context, title: "OTP Sent", message: authProvider.mobileResponse?.message ?? "OTP sent successfully.", ); setState(() { _secondsRemaining = 22; }); for (var controller in _controllers) { controller.clear(); } setState(() { _isValid = false; }); _startTimer(); } else { CustomSnackBar.showWarning( context: context, title: "Error", message: authProvider.mobileResponse?.message ?? "Failed to resend OTP", ); } } catch (e) { CustomSnackBar.showError( context: context, title: "Network Error", message: e.toString(), ); } finally { if (mounted) setState(() => _isResending = false); } } Widget _buildOtpBox(int index) { return SizedBox( width: 55, height: 55, child: TextField( controller: _controllers[index], focusNode: _focusNodes[index], keyboardType: TextInputType.number, textAlign: TextAlign.center, maxLength: 1, style: const TextStyle( fontSize: 22, fontFamily: "Poppins", fontWeight: FontWeight.w400, color: Colors.black, ), decoration: InputDecoration( counterText: "", filled: true, fillColor: const Color(0xFFD7F0FF), enabledBorder: OutlineInputBorder( borderRadius: BorderRadius.circular(30), borderSide: BorderSide(color: Colors.grey.shade300, width: 1), ), focusedBorder: OutlineInputBorder( borderRadius: BorderRadius.circular(30), 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 void dispose() { for (var controller in _controllers) { controller.dispose(); } for (var node in _focusNodes) { node.dispose(); } _timer?.cancel(); super.dispose(); } @override Widget build(BuildContext context) { return Scaffold( backgroundColor: Colors.blue, resizeToAvoidBottomInset: true, body: Stack( children: [ /// 🔹 Background Container( width: double.infinity, height: double.infinity, decoration: const BoxDecoration( image: DecorationImage( image: AssetImage("assets/images/background_png.png"), fit: BoxFit.cover, ), ), ), SafeArea( child: LayoutBuilder( builder: (context, constraints) { return SingleChildScrollView( padding: EdgeInsets.only( bottom: MediaQuery.of(context).viewInsets.bottom, ), child: ConstrainedBox( constraints: BoxConstraints(minHeight: constraints.maxHeight), child: IntrinsicHeight( child: Column( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ const SizedBox(height: 80), /// 🔹 Logo Row( mainAxisAlignment: MainAxisAlignment.start, children: [ const SizedBox(width: 30), SvgPicture.asset( "assets/svg/genesis_logo_2io.svg", height: 50, color: Colors.white, ), ], ), const Spacer(), Row( mainAxisAlignment: MainAxisAlignment.start, children: [ SizedBox(width: 6,), Padding( padding: const EdgeInsets.symmetric(horizontal: 30, vertical: 12), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( "Login to", style: TextStyle( fontSize: 48, height: 1, fontFamily: "PoppinsExtraLight", fontWeight: FontWeight.w200, color: Colors.white, ), ), Text( "continue", style: TextStyle( fontSize: 48, fontWeight: FontWeight.w500, color: Colors.white, ), ), ], ), ), ], ), SizedBox(height: 20,), /// 🔹 Bottom sheet area Container( width: double.infinity, padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 30), decoration: const BoxDecoration( color: Colors.white, borderRadius: BorderRadius.only( topLeft: Radius.circular(28), topRight: Radius.circular(28), ), ), child: Column( crossAxisAlignment: CrossAxisAlignment.center, children: [ Text( "Enter the OTP, sent to +91 ${widget.mob}", style: const TextStyle( color: Colors.black87, fontSize: 14, fontWeight: FontWeight.w500, ), ), const SizedBox(height: 24), Row( mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: List.generate(4, (i) => _buildOtpBox(i)), ), const SizedBox(height: 16), /// 🔹 Resend OTP TextButton( onPressed: _secondsRemaining == 0 && !_isResending ? () => _resendOtp(context) : null, child: _isResending ? const SizedBox( width: 20, height: 20, child: CircularProgressIndicator( strokeWidth: 2, color: Colors.blue, ), ) : Text( _secondsRemaining == 0 ? "Resend OTP" : "Resend OTP in $_secondsRemaining s", style: TextStyle( color: _secondsRemaining == 0 ? const Color(0xFF008CDE) : Colors.black45, fontSize: 14, decorationColor: const Color(0xFF008CDE), decoration: _secondsRemaining == 0 ? TextDecoration.underline : TextDecoration.none, ), ), ), const SizedBox(height: 4), /// 🔹 Continue Button SizedBox( width: double.infinity, height: 50, child: ElevatedButton( style: ElevatedButton.styleFrom( backgroundColor: const Color(0xFF008CDE), shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(25), ), ), onPressed: _isVerifying ? null : _verifyOtp, child: _isVerifying ? const CircularProgressIndicator( strokeWidth: 2, color: Colors.white, ) : const Text( "Continue", style: TextStyle( color: Colors.white, fontSize: 16, fontWeight: FontWeight.w600, ), ), ), ), const SizedBox(height: 12), ], ), ), ], ), ), ), ); }, ), ), ], ), ); } }