218 lines
5.8 KiB
Dart
218 lines
5.8 KiB
Dart
import 'dart:async';
|
|
import 'package:flutter/material.dart';
|
|
import 'package:get/get.dart';
|
|
import 'package:marco/helpers/widgets/my_snackbar.dart';
|
|
import 'package:marco/helpers/services/auth_service.dart';
|
|
import 'package:marco/helpers/services/storage/local_storage.dart';
|
|
import 'package:marco/helpers/services/app_logger.dart';
|
|
|
|
class OTPController extends GetxController {
|
|
final formKey = GlobalKey<FormState>();
|
|
|
|
final RxString email = ''.obs;
|
|
final RxBool isOTPSent = false.obs;
|
|
final RxBool isSending = false.obs;
|
|
final RxBool isResending = false.obs;
|
|
final RxInt timer = 0.obs;
|
|
Timer? _countdownTimer;
|
|
|
|
final TextEditingController emailController = TextEditingController();
|
|
final List<TextEditingController> otpControllers =
|
|
List.generate(4, (_) => TextEditingController());
|
|
final List<FocusNode> focusNodes = List.generate(4, (_) => FocusNode());
|
|
|
|
@override
|
|
void onInit() {
|
|
super.onInit();
|
|
timer.value = 0;
|
|
_loadSavedEmail();
|
|
logSafe("[OTPController] Initialized");
|
|
}
|
|
|
|
@override
|
|
void onClose() {
|
|
_countdownTimer?.cancel();
|
|
emailController.dispose();
|
|
for (final controller in otpControllers) {
|
|
controller.dispose();
|
|
}
|
|
for (final node in focusNodes) {
|
|
node.dispose();
|
|
}
|
|
logSafe("[OTPController] Disposed");
|
|
super.onClose();
|
|
}
|
|
|
|
Future<bool> _sendOTP(String email) async {
|
|
logSafe("[OTPController] Sending OTP");
|
|
final result = await AuthService.generateOtp(email);
|
|
if (result == null) {
|
|
logSafe("[OTPController] OTP sent successfully");
|
|
return true;
|
|
} else {
|
|
logSafe(
|
|
"[OTPController] OTP send failed",
|
|
level: LogLevel.warning,
|
|
error: result['error'],
|
|
);
|
|
showAppSnackbar(
|
|
title: "Error",
|
|
message: result['error'] ?? "Failed to send OTP",
|
|
type: SnackbarType.error,
|
|
);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
Future<void> sendOTP() async {
|
|
final userEmail = emailController.text.trim();
|
|
logSafe("[OTPController] sendOTP called");
|
|
|
|
if (!_validateEmail(userEmail)) {
|
|
logSafe("[OTPController] Invalid email format", level: LogLevel.warning);
|
|
showAppSnackbar(
|
|
title: "Error",
|
|
message: "Please enter a valid email address",
|
|
type: SnackbarType.error,
|
|
);
|
|
return;
|
|
}
|
|
|
|
if (isSending.value) return;
|
|
isSending.value = true;
|
|
|
|
final success = await _sendOTP(userEmail);
|
|
if (success) {
|
|
email.value = userEmail;
|
|
isOTPSent.value = true;
|
|
await _saveEmailIfRemembered(userEmail);
|
|
_startTimer();
|
|
_clearOTPFields();
|
|
}
|
|
|
|
isSending.value = false;
|
|
}
|
|
|
|
Future<void> onResendOTP() async {
|
|
if (isResending.value) return;
|
|
logSafe("[OTPController] Resending OTP");
|
|
|
|
isResending.value = true;
|
|
_clearOTPFields();
|
|
|
|
final success = await _sendOTP(email.value);
|
|
if (success) {
|
|
_startTimer();
|
|
}
|
|
|
|
isResending.value = false;
|
|
}
|
|
|
|
void onOTPChanged(String value, int index) {
|
|
logSafe("[OTPController] OTP field changed: index=$index",
|
|
level: LogLevel.debug);
|
|
if (value.isNotEmpty) {
|
|
if (index < otpControllers.length - 1) {
|
|
focusNodes[index + 1].requestFocus();
|
|
} else {
|
|
focusNodes[index].unfocus();
|
|
}
|
|
} else {
|
|
if (index > 0) {
|
|
focusNodes[index - 1].requestFocus();
|
|
}
|
|
}
|
|
}
|
|
|
|
Future<void> verifyOTP() async {
|
|
final enteredOTP = otpControllers.map((c) => c.text).join();
|
|
final result = await AuthService.verifyOtp(
|
|
email: email.value,
|
|
otp: enteredOTP,
|
|
);
|
|
|
|
if (result == null) {
|
|
// ✅ Handle remember-me like in LoginController
|
|
final remember = LocalStorage.getBool('remember_me') ?? false;
|
|
if (remember) await LocalStorage.setToken('otp_email', email.value);
|
|
|
|
// ✅ Enable remote logging
|
|
enableRemoteLogging();
|
|
|
|
Get.offAllNamed('/select-tenant');
|
|
} else {
|
|
showAppSnackbar(
|
|
title: "Error",
|
|
message: result['error']!,
|
|
type: SnackbarType.error,
|
|
);
|
|
}
|
|
}
|
|
|
|
void _clearOTPFields() {
|
|
logSafe("[OTPController] Clearing OTP input fields", level: LogLevel.debug);
|
|
for (final controller in otpControllers) {
|
|
controller.clear();
|
|
}
|
|
focusNodes[0].requestFocus();
|
|
}
|
|
|
|
void _startTimer() {
|
|
logSafe("[OTPController] Starting resend timer");
|
|
timer.value = 60;
|
|
_countdownTimer?.cancel();
|
|
_countdownTimer = Timer.periodic(const Duration(seconds: 1), (timer) {
|
|
if (this.timer.value > 0) {
|
|
this.timer.value--;
|
|
} else {
|
|
timer.cancel();
|
|
}
|
|
});
|
|
}
|
|
|
|
void resetForChangeEmail() {
|
|
logSafe("[OTPController] Resetting OTP form for change email");
|
|
|
|
isOTPSent.value = false;
|
|
email.value = '';
|
|
emailController.clear();
|
|
_clearOTPFields();
|
|
|
|
timer.value = 0;
|
|
isSending.value = false;
|
|
isResending.value = false;
|
|
|
|
for (final node in focusNodes) {
|
|
node.unfocus();
|
|
}
|
|
|
|
// Optionally remove saved email
|
|
LocalStorage.removeToken('otp_email');
|
|
}
|
|
|
|
bool _validateEmail(String email) {
|
|
final regex = RegExp(r'^[\w-\.]+@([\w-]+\.)+[\w-]{2,}$');
|
|
return regex.hasMatch(email);
|
|
}
|
|
|
|
/// Save email to local storage if "remember me" is set
|
|
Future<void> _saveEmailIfRemembered(String email) async {
|
|
final remember = LocalStorage.getBool('remember_me') ?? false;
|
|
if (remember) {
|
|
await LocalStorage.setToken('otp_email', email);
|
|
}
|
|
}
|
|
|
|
/// Load email from local storage if "remember me" is true
|
|
Future<void> _loadSavedEmail() async {
|
|
final remember = LocalStorage.getBool('remember_me') ?? false;
|
|
if (remember) {
|
|
final savedEmail = LocalStorage.getToken('otp_email') ?? '';
|
|
emailController.text = savedEmail;
|
|
email.value = savedEmail;
|
|
logSafe(
|
|
"[OTPController] Loaded saved email from local storage: $savedEmail");
|
|
}
|
|
}
|
|
}
|