322 lines
9.2 KiB
Dart
322 lines
9.2 KiB
Dart
import 'package:flutter/material.dart';
|
|
import 'package:get/get.dart';
|
|
import 'package:marco/helpers/services/auth_service.dart';
|
|
import 'package:marco/helpers/services/storage/local_storage.dart';
|
|
import 'package:marco/helpers/widgets/my_form_validator.dart';
|
|
import 'package:marco/helpers/widgets/my_snackbar.dart';
|
|
import 'package:marco/helpers/services/app_logger.dart';
|
|
import 'package:marco/helpers/services/firebase/firebase_messaging_service.dart';
|
|
import 'package:marco/controller/permission_controller.dart';
|
|
import 'package:marco/controller/project_controller.dart';
|
|
|
|
class MPINController extends GetxController {
|
|
final MyFormValidator basicValidator = MyFormValidator();
|
|
final isNewUser = false.obs;
|
|
final isChangeMpin = false.obs;
|
|
final RxBool isLoading = false.obs;
|
|
final formKey = GlobalKey<FormState>();
|
|
|
|
// Updated to 4-digit MPIN
|
|
final digitControllers = List.generate(4, (_) => TextEditingController());
|
|
final focusNodes = List.generate(4, (_) => FocusNode());
|
|
|
|
final retypeControllers = List.generate(4, (_) => TextEditingController());
|
|
final retypeFocusNodes = List.generate(4, (_) => FocusNode());
|
|
|
|
final RxInt failedAttempts = 0.obs;
|
|
|
|
@override
|
|
void onInit() {
|
|
super.onInit();
|
|
final bool hasMpin = LocalStorage.getIsMpin();
|
|
isNewUser.value = !hasMpin;
|
|
logSafe("onInit called. isNewUser: ${isNewUser.value}");
|
|
}
|
|
|
|
/// Enable Change MPIN mode
|
|
void setChangeMpinMode() {
|
|
isChangeMpin.value = true;
|
|
isNewUser.value = false;
|
|
clearFields();
|
|
clearRetypeFields();
|
|
logSafe("setChangeMpinMode activated");
|
|
}
|
|
|
|
/// Handle digit entry and focus movement
|
|
void onDigitChanged(String value, int index, {bool isRetype = false}) {
|
|
logSafe(
|
|
"onDigitChanged -> index: $index, value: $value, isRetype: $isRetype");
|
|
final nodes = isRetype ? retypeFocusNodes : focusNodes;
|
|
if (value.isNotEmpty && index < 3) {
|
|
nodes[index + 1].requestFocus();
|
|
} else if (value.isEmpty && index > 0) {
|
|
nodes[index - 1].requestFocus();
|
|
}
|
|
}
|
|
|
|
/// Submit MPIN for verification or generation
|
|
Future<void> onSubmitMPIN() async {
|
|
logSafe("onSubmitMPIN triggered");
|
|
|
|
if (!formKey.currentState!.validate()) {
|
|
logSafe("Form validation failed", level: LogLevel.warning);
|
|
return;
|
|
}
|
|
|
|
final enteredMPIN = digitControllers.map((c) => c.text).join();
|
|
logSafe("Entered MPIN: $enteredMPIN");
|
|
|
|
if (enteredMPIN.length < 4) {
|
|
_showError("Please enter all 4 digits.");
|
|
return;
|
|
}
|
|
|
|
if (isNewUser.value || isChangeMpin.value) {
|
|
final retypeMPIN = retypeControllers.map((c) => c.text).join();
|
|
logSafe("Retyped MPIN: $retypeMPIN");
|
|
|
|
if (retypeMPIN.length < 4) {
|
|
_showError("Please enter all 4 digits in Retype MPIN.");
|
|
return;
|
|
}
|
|
|
|
if (enteredMPIN != retypeMPIN) {
|
|
_showError("MPIN and Retype MPIN do not match.");
|
|
clearFields();
|
|
clearRetypeFields();
|
|
return;
|
|
}
|
|
|
|
final bool success = await generateMPIN(mpin: enteredMPIN);
|
|
|
|
if (success) {
|
|
logSafe("MPIN generation/change successful.");
|
|
showAppSnackbar(
|
|
title: "Success",
|
|
message: isChangeMpin.value
|
|
? "MPIN changed successfully."
|
|
: "MPIN generated successfully. Please login again.",
|
|
type: SnackbarType.success,
|
|
);
|
|
await LocalStorage.logout();
|
|
} else {
|
|
logSafe("MPIN generation/change failed.", level: LogLevel.warning);
|
|
clearFields();
|
|
clearRetypeFields();
|
|
}
|
|
} else {
|
|
logSafe("Existing user. Proceeding to verify MPIN.");
|
|
await verifyMPIN();
|
|
}
|
|
}
|
|
|
|
/// Forgot MPIN
|
|
Future<void> onForgotMPIN() async {
|
|
logSafe("onForgotMPIN called");
|
|
isNewUser.value = true;
|
|
isChangeMpin.value = false;
|
|
clearFields();
|
|
clearRetypeFields();
|
|
}
|
|
|
|
/// Switch to login/enter MPIN screen
|
|
void switchToEnterMPIN() {
|
|
logSafe("switchToEnterMPIN called");
|
|
isNewUser.value = false;
|
|
isChangeMpin.value = false;
|
|
clearFields();
|
|
clearRetypeFields();
|
|
}
|
|
|
|
/// Show error snackbar
|
|
void _showError(String message) {
|
|
logSafe("ERROR: $message", level: LogLevel.error);
|
|
showAppSnackbar(
|
|
title: "Error",
|
|
message: message,
|
|
type: SnackbarType.error,
|
|
);
|
|
}
|
|
|
|
/// Navigate to dashboard
|
|
/// Navigate to tenant selection after MPIN verification
|
|
void _navigateToTenantSelection({String? message}) {
|
|
if (message != null) {
|
|
logSafe("Navigating to Tenant Selection with message: $message");
|
|
showAppSnackbar(
|
|
title: "Success",
|
|
message: message,
|
|
type: SnackbarType.success,
|
|
);
|
|
}
|
|
Get.offAllNamed('/select-tenant');
|
|
}
|
|
|
|
/// Clear the primary MPIN fields
|
|
void clearFields() {
|
|
logSafe("clearFields called");
|
|
for (final c in digitControllers) {
|
|
c.clear();
|
|
}
|
|
focusNodes.first.requestFocus();
|
|
}
|
|
|
|
/// Clear the retype MPIN fields
|
|
void clearRetypeFields() {
|
|
logSafe("clearRetypeFields called");
|
|
for (final c in retypeControllers) {
|
|
c.clear();
|
|
}
|
|
retypeFocusNodes.first.requestFocus();
|
|
}
|
|
|
|
/// Cleanup
|
|
@override
|
|
void onClose() {
|
|
logSafe("onClose called");
|
|
for (final controller in digitControllers) {
|
|
controller.dispose();
|
|
}
|
|
for (final node in focusNodes) {
|
|
node.dispose();
|
|
}
|
|
for (final controller in retypeControllers) {
|
|
controller.dispose();
|
|
}
|
|
for (final node in retypeFocusNodes) {
|
|
node.dispose();
|
|
}
|
|
super.onClose();
|
|
}
|
|
|
|
/// Generate MPIN for new user/change MPIN
|
|
Future<bool> generateMPIN({required String mpin}) async {
|
|
try {
|
|
isLoading.value = true;
|
|
logSafe("generateMPIN started");
|
|
|
|
final employeeInfo = LocalStorage.getEmployeeInfo();
|
|
final String? employeeId = employeeInfo?.id;
|
|
|
|
if (employeeId == null || employeeId.isEmpty) {
|
|
isLoading.value = false;
|
|
_showError("Missing employee ID.");
|
|
return false;
|
|
}
|
|
|
|
logSafe("Calling AuthService.generateMpin for employeeId: $employeeId");
|
|
|
|
final response = await AuthService.generateMpin(
|
|
employeeId: employeeId,
|
|
mpin: mpin,
|
|
);
|
|
|
|
isLoading.value = false;
|
|
|
|
if (response == null) {
|
|
return true;
|
|
} else {
|
|
logSafe("MPIN generation returned error: $response",
|
|
level: LogLevel.warning);
|
|
showAppSnackbar(
|
|
title: "MPIN Operation Failed",
|
|
message: "Please check your inputs.",
|
|
type: SnackbarType.error,
|
|
);
|
|
basicValidator.addErrors(response);
|
|
basicValidator.validateForm();
|
|
basicValidator.clearErrors();
|
|
return false;
|
|
}
|
|
} catch (e) {
|
|
isLoading.value = false;
|
|
logSafe("Exception in generateMPIN", level: LogLevel.error, error: e);
|
|
_showError("Failed to process MPIN.");
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/// Verify MPIN for existing user
|
|
Future<void> verifyMPIN() async {
|
|
logSafe("verifyMPIN triggered");
|
|
|
|
final enteredMPIN = digitControllers.map((c) => c.text).join();
|
|
if (enteredMPIN.length < 4) {
|
|
_showError("Please enter all 4 digits.");
|
|
return;
|
|
}
|
|
|
|
final mpinToken = await LocalStorage.getMpinToken();
|
|
if (mpinToken == null || mpinToken.isEmpty) {
|
|
_showError("Missing MPIN token. Please log in again.");
|
|
return;
|
|
}
|
|
|
|
try {
|
|
isLoading.value = true;
|
|
|
|
final fcmToken = await FirebaseNotificationService().getFcmToken();
|
|
|
|
final response = await AuthService.verifyMpin(
|
|
mpin: enteredMPIN,
|
|
mpinToken: mpinToken,
|
|
fcmToken: fcmToken ?? '',
|
|
);
|
|
|
|
isLoading.value = false;
|
|
|
|
if (response == null) {
|
|
logSafe("MPIN verified successfully");
|
|
await LocalStorage.setBool('mpin_verified', true);
|
|
|
|
// 🔹 Ensure controllers are injected and loaded
|
|
final token = await LocalStorage.getJwtToken();
|
|
if (token != null && token.isNotEmpty) {
|
|
if (!Get.isRegistered<PermissionController>()) {
|
|
Get.put(PermissionController());
|
|
await Get.find<PermissionController>().loadData(token);
|
|
}
|
|
if (!Get.isRegistered<ProjectController>()) {
|
|
Get.put(ProjectController(), permanent: true);
|
|
await Get.find<ProjectController>().fetchProjects();
|
|
}
|
|
}
|
|
|
|
showAppSnackbar(
|
|
title: "Success",
|
|
message: "MPIN Verified Successfully",
|
|
type: SnackbarType.success,
|
|
);
|
|
_navigateToTenantSelection();
|
|
} else {
|
|
final errorMessage = response["error"] ?? "Invalid MPIN";
|
|
logSafe("MPIN verification failed: $errorMessage",
|
|
level: LogLevel.warning);
|
|
showAppSnackbar(
|
|
title: "Error",
|
|
message: errorMessage,
|
|
type: SnackbarType.error,
|
|
);
|
|
clearFields();
|
|
onInvalidMPIN();
|
|
}
|
|
} catch (e) {
|
|
isLoading.value = false;
|
|
logSafe("Exception in verifyMPIN", level: LogLevel.error, error: e);
|
|
_showError("Something went wrong. Please try again.");
|
|
}
|
|
}
|
|
|
|
/// Increment failed attempts and warn
|
|
void onInvalidMPIN() {
|
|
failedAttempts.value++;
|
|
if (failedAttempts.value >= 3) {
|
|
showAppSnackbar(
|
|
title: "Error",
|
|
message: "Too many failed attempts. Consider logging in again.",
|
|
type: SnackbarType.error,
|
|
);
|
|
}
|
|
}
|
|
}
|