278 lines
9.1 KiB
Dart
278 lines
9.1 KiB
Dart
import 'dart:async';
|
|
import 'package:flutter/material.dart';
|
|
import 'package:pin_code_fields/pin_code_fields.dart';
|
|
import 'package:supabase_flutter/supabase_flutter.dart';
|
|
|
|
class OtpScreen extends StatefulWidget {
|
|
final String email;
|
|
final String userId;
|
|
|
|
const OtpScreen({
|
|
super.key,
|
|
required this.email,
|
|
required this.userId,
|
|
});
|
|
|
|
@override
|
|
State<OtpScreen> createState() => _OtpScreenState();
|
|
}
|
|
|
|
class _OtpScreenState extends State<OtpScreen> {
|
|
final _otpController = TextEditingController();
|
|
bool _isLoading = false;
|
|
int _resendCooldown = 60;
|
|
String _enteredOtp = '';
|
|
late Timer _timer;
|
|
final Color primaryColor = const Color(0xFF056839); // Dark Green from your branding
|
|
|
|
@override
|
|
void initState() {
|
|
super.initState();
|
|
_startCooldown();
|
|
// Consider sending an initial OTP request here if not done prior to this screen
|
|
WidgetsBinding.instance.addPostFrameCallback((_) {
|
|
if (mounted) { // Ensure the widget is still mounted
|
|
_showInitialOtpInfo();
|
|
}
|
|
});
|
|
}
|
|
|
|
void _showInitialOtpInfo() {
|
|
_showSuccessSnackbar('Kode OTP telah dikirim ke email Anda untuk verifikasi akun.');
|
|
}
|
|
|
|
void _startCooldown() {
|
|
_timer = Timer.periodic(const Duration(seconds: 1), (timer) {
|
|
if (!mounted) {
|
|
timer.cancel();
|
|
return;
|
|
}
|
|
if (_resendCooldown > 0) {
|
|
setState(() => _resendCooldown--);
|
|
} else {
|
|
timer.cancel();
|
|
}
|
|
});
|
|
}
|
|
|
|
Future<void> _verifyOtp(String otp) async {
|
|
if (!mounted) return;
|
|
setState(() => _isLoading = true);
|
|
|
|
try {
|
|
final cleanOtp = otp.trim();
|
|
if (cleanOtp.length != 6) {
|
|
_showErrorSnackbar('Kode OTP harus 6 digit.');
|
|
return;
|
|
}
|
|
|
|
final AuthResponse res = await Supabase.instance.client.auth.verifyOTP(
|
|
email: widget.email,
|
|
token: cleanOtp,
|
|
type: OtpType.signup,
|
|
);
|
|
|
|
if (res.session == null) {
|
|
if (!mounted) return;
|
|
_showErrorSnackbar('OTP tidak valid atau telah kedaluwarsa.');
|
|
return;
|
|
}
|
|
|
|
if (!mounted) return;
|
|
_showSuccessSnackbar('Selamat Datang di Aplikasi TaniSM4RT!');
|
|
Navigator.of(context).pushNamedAndRemoveUntil(
|
|
'/home',
|
|
(Route<dynamic> route) => false,
|
|
);
|
|
|
|
} catch (e) {
|
|
String errorMessage = 'Verifikasi gagal. Silakan coba lagi.';
|
|
if (e is AuthException) {
|
|
if (e.statusCode == '403' || e.message.contains('Token has expired or is invalid')) {
|
|
errorMessage = 'Kode OTP telah kedaluwarsa atau tidak valid. Silakan minta kode baru.';
|
|
} else if (e.message.contains('Invalid OTP')) {
|
|
errorMessage = 'Kode OTP tidak valid. Silakan coba lagi.';
|
|
}
|
|
} else {
|
|
errorMessage = 'Verifikasi gagal karena kesalahan teknis. Mohon coba lagi nanti.';
|
|
}
|
|
_otpController.clear();
|
|
setState(() => _enteredOtp = '');
|
|
_showErrorSnackbar(errorMessage);
|
|
} finally {
|
|
if (mounted) setState(() => _isLoading = false);
|
|
}
|
|
}
|
|
|
|
Future<void> _resendOtp() async {
|
|
if (!mounted) return;
|
|
setState(() {
|
|
_resendCooldown = 60;
|
|
_isLoading = true;
|
|
});
|
|
_startCooldown();
|
|
|
|
try {
|
|
// Resend OTP for email verification
|
|
await Supabase.instance.client.auth.resend(
|
|
type: OtpType.signup,
|
|
email: widget.email
|
|
);
|
|
_showSuccessSnackbar('Kode OTP baru telah dikirim. Silakan cek email Anda (termasuk folder spam).');
|
|
_otpController.clear();
|
|
setState(() => _enteredOtp = '');
|
|
} catch (e) {
|
|
String errorMessage = 'Gagal mengirim ulang OTP. Mohon coba lagi nanti.';
|
|
if (e is AuthException) {
|
|
errorMessage = 'Gagal mengirim ulang OTP. Periksa koneksi atau coba beberapa saat lagi.';
|
|
} else {
|
|
errorMessage = 'Gagal mengirim ulang OTP karena kesalahan teknis. Mohon coba lagi nanti.';
|
|
}
|
|
_showErrorSnackbar(errorMessage);
|
|
} finally {
|
|
if (mounted) setState(() => _isLoading = false);
|
|
}
|
|
}
|
|
|
|
void _showErrorSnackbar(String message) {
|
|
if (!mounted) return;
|
|
ScaffoldMessenger.of(context).showSnackBar(
|
|
SnackBar(
|
|
content: Text(message, style: const TextStyle(color: Colors.white)),
|
|
backgroundColor: Colors.redAccent,
|
|
behavior: SnackBarBehavior.floating,
|
|
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(10)),
|
|
duration: const Duration(seconds: 3),
|
|
),
|
|
);
|
|
}
|
|
|
|
void _showSuccessSnackbar(String message) {
|
|
if (!mounted) return;
|
|
ScaffoldMessenger.of(context).showSnackBar(
|
|
SnackBar(
|
|
content: Text(message, style: const TextStyle(color: Colors.white)),
|
|
backgroundColor: primaryColor, // Use primaryColor for success
|
|
behavior: SnackBarBehavior.floating,
|
|
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(10)),
|
|
duration: const Duration(seconds: 3),
|
|
),
|
|
);
|
|
}
|
|
|
|
@override
|
|
void dispose() {
|
|
_timer.cancel();
|
|
_otpController.dispose();
|
|
super.dispose();
|
|
}
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
final theme = Theme.of(context);
|
|
|
|
return Scaffold(
|
|
backgroundColor: Colors.grey[100],
|
|
appBar: AppBar(
|
|
title: const Text(
|
|
'Verifikasi Akun',
|
|
style: TextStyle(color: Colors.white, fontWeight: FontWeight.bold),
|
|
),
|
|
backgroundColor: primaryColor,
|
|
elevation: 0,
|
|
iconTheme: const IconThemeData(color: Colors.white),
|
|
),
|
|
body: SingleChildScrollView(
|
|
padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 32),
|
|
child: Column(
|
|
mainAxisAlignment: MainAxisAlignment.center,
|
|
crossAxisAlignment: CrossAxisAlignment.stretch,
|
|
children: [
|
|
Image.asset('assets/images/logo.png', height: 100),
|
|
const SizedBox(height: 32),
|
|
Text(
|
|
'Verifikasi Email Anda',
|
|
textAlign: TextAlign.center,
|
|
style: theme.textTheme.headlineSmall?.copyWith(
|
|
fontWeight: FontWeight.bold,
|
|
color: Colors.black87,
|
|
),
|
|
),
|
|
const SizedBox(height: 12),
|
|
Text(
|
|
'Kode verifikasi 6 digit telah dikirim ke email Anda:',
|
|
textAlign: TextAlign.center,
|
|
style: theme.textTheme.bodyMedium?.copyWith(color: Colors.black54),
|
|
),
|
|
const SizedBox(height: 8),
|
|
Text(
|
|
widget.email,
|
|
textAlign: TextAlign.center,
|
|
style: theme.textTheme.bodyLarge?.copyWith(
|
|
color: primaryColor,
|
|
fontWeight: FontWeight.bold,
|
|
),
|
|
),
|
|
const SizedBox(height: 32),
|
|
PinCodeTextField(
|
|
appContext: context,
|
|
length: 6,
|
|
controller: _otpController,
|
|
keyboardType: TextInputType.number,
|
|
animationType: AnimationType.fade,
|
|
pinTheme: PinTheme(
|
|
shape: PinCodeFieldShape.box,
|
|
borderRadius: BorderRadius.circular(12),
|
|
fieldHeight: 50,
|
|
fieldWidth: 45,
|
|
activeFillColor: Colors.white,
|
|
inactiveFillColor: Colors.white,
|
|
selectedFillColor: Colors.white,
|
|
activeColor: primaryColor,
|
|
inactiveColor: Colors.grey.shade300,
|
|
selectedColor: primaryColor,
|
|
borderWidth: 1,
|
|
),
|
|
enableActiveFill: true,
|
|
onChanged: (value) {
|
|
if (mounted) setState(() => _enteredOtp = value);
|
|
},
|
|
onCompleted: (otp) {
|
|
if (mounted) {
|
|
setState(() => _enteredOtp = otp);
|
|
_verifyOtp(otp);
|
|
}
|
|
},
|
|
beforeTextPaste: (text) => true, // Allow pasting
|
|
autoDisposeControllers: false, // Important for manual controller management
|
|
),
|
|
const SizedBox(height: 24),
|
|
ElevatedButton(
|
|
style: ElevatedButton.styleFrom(
|
|
backgroundColor: primaryColor,
|
|
padding: const EdgeInsets.symmetric(vertical: 16),
|
|
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
|
|
elevation: 2,
|
|
),
|
|
onPressed: _isLoading || _enteredOtp.length != 6 ? null : () => _verifyOtp(_enteredOtp),
|
|
child: _isLoading
|
|
? const SizedBox(height: 24, width: 24, child: CircularProgressIndicator(color: Colors.white, strokeWidth: 3))
|
|
: const Text('Verifikasi Kode', style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold, color: Colors.white)),
|
|
),
|
|
const SizedBox(height: 16),
|
|
TextButton(
|
|
onPressed: _resendCooldown > 0 || _isLoading ? null : _resendOtp,
|
|
child: Text(
|
|
_resendCooldown > 0
|
|
? 'Kirim ulang OTP dalam $_resendCooldown detik'
|
|
: 'Kirim Ulang OTP',
|
|
style: TextStyle(color: _resendCooldown > 0 ? Colors.grey : primaryColor, fontWeight: FontWeight.bold),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
);
|
|
}
|
|
}
|