- Introduced a new `logSafe` function for consistent logging with sensitivity handling. - Replaced direct logger calls with `logSafe` in `api_service.dart`, `app_initializer.dart`, `auth_service.dart`, `permission_service.dart`, and `my_image_compressor.dart`. - Enhanced error handling and logging in various service methods to capture exceptions and provide more context. - Updated image compression logging to include quality and size metrics. - Improved app initialization logging to capture success and error states. - Ensured sensitive information is not logged directly.
199 lines
5.2 KiB
Dart
199 lines
5.2 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;
|
|
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;
|
|
_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();
|
|
logSafe("[OTPController] Verifying OTP");
|
|
|
|
final result = await AuthService.verifyOtp(
|
|
email: email.value,
|
|
otp: enteredOTP,
|
|
);
|
|
|
|
if (result == null) {
|
|
logSafe("[OTPController] OTP verified successfully");
|
|
showAppSnackbar(
|
|
title: "Success",
|
|
message: "OTP verified successfully",
|
|
type: SnackbarType.success,
|
|
);
|
|
final bool isMpinEnabled = LocalStorage.getIsMpin();
|
|
logSafe("[OTPController] MPIN Enabled: $isMpinEnabled");
|
|
|
|
Get.offAllNamed('/home');
|
|
} else {
|
|
final error = result['error'] ?? "Failed to verify OTP";
|
|
logSafe("[OTPController] OTP verification failed", level: LogLevel.warning, error: error, sensitive: true);
|
|
showAppSnackbar(
|
|
title: "Error",
|
|
message: 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();
|
|
}
|
|
}
|
|
|
|
bool _validateEmail(String email) {
|
|
final regex = RegExp(r'^[\w-\.]+@([\w-]+\.)+[\w-]{2,}$');
|
|
return regex.hasMatch(email);
|
|
}
|
|
}
|