MIF_E31231033/lib/screens/auth/verify_reset_otp_screen.dart

317 lines
8.2 KiB
Dart

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<VerifyResetOtpScreen> createState() => _VerifyResetOtpScreenState();
}
class _VerifyResetOtpScreenState extends State<VerifyResetOtpScreen> {
final List<TextEditingController> controllers = List.generate(
6,
(_) => TextEditingController(),
);
final List<FocusNode> 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<void> 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,
),
),
),
],
),
),
),
),
);
}
}