import 'dart:async'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import '../../services/auth_service.dart'; import 'reset_password_screen.dart'; class VerifyResetOtpScreen extends StatefulWidget { final String email; const VerifyResetOtpScreen({super.key, required this.email}); @override State createState() => _VerifyResetOtpScreenState(); } class _VerifyResetOtpScreenState extends State { final List controllers = List.generate( 6, (_) => TextEditingController(), ); final List focusNodes = List.generate(6, (_) => FocusNode()); bool isLoading = false; bool isResendLoading = false; int resendTimer = 60; Timer? timer; @override void initState() { super.initState(); startTimer(); } void startTimer() { resendTimer = 60; timer = Timer.periodic(const Duration(seconds: 1), (t) { if (resendTimer == 0) { t.cancel(); } else { setState(() { resendTimer--; }); } }); } String getOtp() { return controllers.map((e) => e.text.trim()).join(); } Future verifyOtp() async { String otp = getOtp(); if (otp.length != 6) { _showSnackBar("OTP harus 6 digit", Colors.orange); return; } setState(() => isLoading = true); final result = await AuthService.verifyResetOtp( email: widget.email, otp: otp, ); setState(() => isLoading = false); if (!mounted) return; if (result['success']) { Navigator.push( context, MaterialPageRoute( builder: (_) => ResetPasswordScreen(email: widget.email, otp: otp), ), ); } else { _showSnackBar(result['message'] ?? "OTP salah", Colors.red); } } void resendOtp() async { setState(() { isResendLoading = true; }); final result = await AuthService.forgotPassword(widget.email); setState(() { isResendLoading = false; }); if (!mounted) return; if (result['status'] == true) { _showSnackBar("OTP berhasil dikirim ulang", Colors.green); startTimer(); } else { _showSnackBar(result['message'], Colors.red); } } void onOtpChanged(int index, String value) { /// jika user paste OTP (misal 123456) if (value.length > 1) { final otpChars = value.split(''); for (int i = 0; i < otpChars.length && i < 6; i++) { controllers[i].text = otpChars[i]; } focusNodes[5].requestFocus(); if (otpChars.length == 6) { verifyOtp(); } setState(() {}); return; } /// jika mengetik angka → pindah ke kotak berikutnya if (value.isNotEmpty) { if (index < 5) { focusNodes[index + 1].requestFocus(); } } /// jika menghapus → langsung pindah ke kotak sebelumnya if (value.isEmpty) { if (index > 0) { focusNodes[index - 1].requestFocus(); controllers[index - 1].selection = TextSelection.fromPosition( TextPosition(offset: controllers[index - 1].text.length), ); } } String otp = getOtp(); if (otp.length == 6) { verifyOtp(); } } Widget buildOtpBox(int index) { return SizedBox( width: 45, child: TextField( controller: controllers[index], focusNode: focusNodes[index], keyboardType: TextInputType.number, textAlign: TextAlign.center, maxLength: 1, autofillHints: const [AutofillHints.oneTimeCode], inputFormatters: [FilteringTextInputFormatter.digitsOnly], decoration: InputDecoration( counterText: "", border: OutlineInputBorder(borderRadius: BorderRadius.circular(12)), ), onChanged: (value) { onOtpChanged(index, value); }, ), ); } void _showSnackBar(String msg, Color color) { ScaffoldMessenger.of(context).showSnackBar( SnackBar( content: Text(msg), backgroundColor: color, behavior: SnackBarBehavior.floating, shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(10)), ), ); } @override void dispose() { timer?.cancel(); for (var c in controllers) { c.dispose(); } for (var f in focusNodes) { f.dispose(); } super.dispose(); } @override Widget build(BuildContext context) { return Scaffold( backgroundColor: const Color(0xFFF5F7FA), body: SafeArea( child: Center( child: SingleChildScrollView( padding: const EdgeInsets.all(24), child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ Image.asset('assets/images/logopolije.png', height: 100), const SizedBox(height: 16), const Text( "Verifikasi OTP", style: TextStyle(fontSize: 24, fontWeight: FontWeight.bold), ), const Text( "Masukkan kode OTP untuk reset password", style: TextStyle(color: Colors.grey), textAlign: TextAlign.center, ), const SizedBox(height: 30), Container( padding: const EdgeInsets.all(20), decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.circular(20), boxShadow: [ BoxShadow( color: Colors.black.withOpacity(0.05), blurRadius: 20, offset: const Offset(0, 10), ), ], ), child: Column( children: [ Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: List.generate(6, buildOtpBox), ), const SizedBox(height: 25), SizedBox( width: double.infinity, height: 55, child: ElevatedButton( onPressed: isLoading ? null : verifyOtp, style: ElevatedButton.styleFrom( backgroundColor: const Color(0xFF2F5BEA), shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(12), ), ), child: isLoading ? const CircularProgressIndicator( color: Colors.white, ) : const Text( "Verifikasi OTP", style: TextStyle( fontWeight: FontWeight.bold, color: Colors.white, ), ), ), ), ], ), ), const SizedBox(height: 25), resendTimer > 0 ? Text( "Kirim ulang OTP dalam $resendTimer detik", style: const TextStyle(color: Colors.grey), ) : isResendLoading ? const Padding( padding: EdgeInsets.all(8.0), child: CircularProgressIndicator(strokeWidth: 2), ) : GestureDetector( onTap: resendOtp, child: const Text( "Kirim ulang OTP", style: TextStyle( color: Color(0xFF2F5BEA), fontWeight: FontWeight.bold, ), ), ), ], ), ), ), ), ); } }