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");
 | |
|     }
 | |
|   }
 | |
| }
 |