diff --git a/lib/controller/auth/forgot_password_controller.dart b/lib/controller/auth/forgot_password_controller.dart index f5fe915..527f879 100644 --- a/lib/controller/auth/forgot_password_controller.dart +++ b/lib/controller/auth/forgot_password_controller.dart @@ -24,47 +24,45 @@ class ForgotPasswordController extends MyController { ); } -Future onForgotPassword() async { - if (!basicValidator.validateForm()) return; + Future onForgotPassword() async { + if (!basicValidator.validateForm()) return; - isLoading.value = true; - final data = basicValidator.getData(); - final email = data['email']?.toString() ?? ''; + isLoading.value = true; + final data = basicValidator.getData(); + final email = data['email']?.toString() ?? ''; - try { - appLogger.i("Forgot password requested for: $email"); - final result = await AuthService.forgotPassword(email); + try { + logSafe("Forgot password requested for: $email", sensitive: true); - if (result == null) { - // Success case + final result = await AuthService.forgotPassword(email); + + if (result == null) { + showAppSnackbar( + title: "Success", + message: "Password reset link has been sent.", + type: SnackbarType.success, + ); + await LocalStorage.logout(); + } else { + final errorMessage = result['error'] ?? "Failed to send reset link. Please try again."; + showAppSnackbar( + title: "Failed", + message: errorMessage, + type: SnackbarType.error, + ); + logSafe("Failed to send reset password email for $email: $errorMessage", level: LogLevel.warning, sensitive: true); + } + } catch (e, stacktrace) { + logSafe("Error during forgot password", level: LogLevel.error, error: e, stackTrace: stacktrace); showAppSnackbar( - title: "Success", - message: "Password reset link has been sent.", - type: SnackbarType.success, - ); - await LocalStorage.logout(); - } else { - // Failure case with error map - final errorMessage = result['error'] ?? "Failed to send reset link. Please try again."; - showAppSnackbar( - title: "Failed", - message: errorMessage, + title: "Error", + message: "Something went wrong. Please try again later.", type: SnackbarType.error, ); - appLogger.w("Failed to send reset password email for $email: $errorMessage"); + } finally { + isLoading.value = false; } - } catch (e, stacktrace) { - appLogger.e("Error during forgot password", error: e, stackTrace: stacktrace); - showAppSnackbar( - title: "Error", - message: "Something went wrong. Please try again later.", - type: SnackbarType.error, - ); - } finally { - isLoading.value = false; } -} - void gotoLogIn() { Get.offAllNamed('/auth/login-option'); diff --git a/lib/controller/auth/login_controller.dart b/lib/controller/auth/login_controller.dart index 4ec562a..af4a62a 100644 --- a/lib/controller/auth/login_controller.dart +++ b/lib/controller/auth/login_controller.dart @@ -55,12 +55,12 @@ class LoginController extends MyController { try { final loginData = basicValidator.getData(); - appLogger.i("Attempting login for user: ${loginData['username']}"); + logSafe("Attempting login for user: ${loginData['username']}", sensitive: true); final errors = await AuthService.loginUser(loginData); if (errors != null) { - appLogger.w("Login failed: $errors"); + logSafe("Login failed for user: ${loginData['username']} with errors: $errors", level: LogLevel.warning, sensitive: true); showAppSnackbar( title: "Login Failed", @@ -73,11 +73,11 @@ class LoginController extends MyController { basicValidator.clearErrors(); } else { await _handleRememberMe(); - appLogger.i("Login successful: ${loginData['username']}"); + logSafe("Login successful for user: ${loginData['username']}", sensitive: true); Get.toNamed('/home'); } } catch (e, stacktrace) { - appLogger.e("Exception during login", error: e, stackTrace: stacktrace); + logSafe("Exception during login", level: LogLevel.error, error: e, stackTrace: stacktrace); showAppSnackbar( title: "Login Error", message: "An unexpected error occurred", @@ -90,10 +90,8 @@ class LoginController extends MyController { Future _handleRememberMe() async { if (isChecked.value) { - await LocalStorage.setToken( - 'username', basicValidator.getController('username')!.text); - await LocalStorage.setToken( - 'password', basicValidator.getController('password')!.text); + await LocalStorage.setToken('username', basicValidator.getController('username')!.text); + await LocalStorage.setToken('password', basicValidator.getController('password')!.text); await LocalStorage.setBool('remember_me', true); } else { await LocalStorage.removeToken('username'); diff --git a/lib/controller/auth/mpin_controller.dart b/lib/controller/auth/mpin_controller.dart index 8d8d530..31ac669 100644 --- a/lib/controller/auth/mpin_controller.dart +++ b/lib/controller/auth/mpin_controller.dart @@ -5,10 +5,9 @@ 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/view/dashboard/dashboard_screen.dart'; -import 'package:marco/helpers/services/app_logger.dart'; +import 'package:marco/helpers/services/app_logger.dart'; class MPINController extends GetxController { - final MyFormValidator basicValidator = MyFormValidator(); final isNewUser = false.obs; final RxBool isLoading = false.obs; @@ -20,17 +19,17 @@ class MPINController extends GetxController { final retypeControllers = List.generate(6, (_) => TextEditingController()); final retypeFocusNodes = List.generate(6, (_) => FocusNode()); final RxInt failedAttempts = 0.obs; + @override void onInit() { super.onInit(); final bool hasMpin = LocalStorage.getIsMpin(); isNewUser.value = !hasMpin; - appLogger.i("[MPINController] onInit called. isNewUser: ${isNewUser.value}"); + logSafe("onInit called. isNewUser: ${isNewUser.value}"); } void onDigitChanged(String value, int index, {bool isRetype = false}) { - appLogger.i( - "[MPINController] onDigitChanged -> index: $index, value: $value, isRetype: $isRetype"); + logSafe("onDigitChanged -> index: $index, value: $value, isRetype: $isRetype", sensitive: true); final nodes = isRetype ? retypeFocusNodes : focusNodes; if (value.isNotEmpty && index < 5) { nodes[index + 1].requestFocus(); @@ -40,15 +39,15 @@ class MPINController extends GetxController { } Future onSubmitMPIN() async { - appLogger.i("[MPINController] onSubmitMPIN triggered"); + logSafe("onSubmitMPIN triggered"); if (!formKey.currentState!.validate()) { - appLogger.w("[MPINController] Form validation failed"); + logSafe("Form validation failed", level: LogLevel.warning); return; } final enteredMPIN = digitControllers.map((c) => c.text).join(); - appLogger.i("[MPINController] Entered MPIN: $enteredMPIN"); + logSafe("Entered MPIN: $enteredMPIN", sensitive: true); if (enteredMPIN.length < 6) { _showError("Please enter all 6 digits."); @@ -57,7 +56,7 @@ class MPINController extends GetxController { if (isNewUser.value) { final retypeMPIN = retypeControllers.map((c) => c.text).join(); - appLogger.i("[MPINController] Retyped MPIN: $retypeMPIN"); + logSafe("Retyped MPIN: $retypeMPIN", sensitive: true); if (retypeMPIN.length < 6) { _showError("Please enter all 6 digits in Retype MPIN."); @@ -71,11 +70,11 @@ class MPINController extends GetxController { return; } - appLogger.i("[MPINController] MPINs matched. Proceeding to generate MPIN."); + logSafe("MPINs matched. Proceeding to generate MPIN."); final bool success = await generateMPIN(mpin: enteredMPIN); if (success) { - appLogger.i("[MPINController] MPIN generation successful."); + logSafe("MPIN generation successful."); showAppSnackbar( title: "Success", message: "MPIN generated successfully. Please login again.", @@ -83,32 +82,32 @@ class MPINController extends GetxController { ); await LocalStorage.logout(); } else { - appLogger.w("[MPINController] MPIN generation failed."); + logSafe("MPIN generation failed.", level: LogLevel.warning); clearFields(); clearRetypeFields(); } } else { - appLogger.i("[MPINController] Existing user. Proceeding to verify MPIN."); + logSafe("Existing user. Proceeding to verify MPIN."); await verifyMPIN(); } } Future onForgotMPIN() async { - appLogger.i("[MPINController] onForgotMPIN called"); + logSafe("onForgotMPIN called"); isNewUser.value = true; clearFields(); clearRetypeFields(); } void switchToEnterMPIN() { - appLogger.i("[MPINController] switchToEnterMPIN called"); + logSafe("switchToEnterMPIN called"); isNewUser.value = false; clearFields(); clearRetypeFields(); } void _showError(String message) { - appLogger.e("[MPINController] ERROR: $message"); + logSafe("ERROR: $message", level: LogLevel.error); showAppSnackbar( title: "Error", message: message, @@ -118,8 +117,7 @@ class MPINController extends GetxController { void _navigateToDashboard({String? message}) { if (message != null) { - appLogger - .i("[MPINController] Navigating to Dashboard with message: $message"); + logSafe("Navigating to Dashboard with message: $message"); showAppSnackbar( title: "Success", message: message, @@ -130,7 +128,7 @@ class MPINController extends GetxController { } void clearFields() { - appLogger.i("[MPINController] clearFields called"); + logSafe("clearFields called"); for (final c in digitControllers) { c.clear(); } @@ -138,7 +136,7 @@ class MPINController extends GetxController { } void clearRetypeFields() { - appLogger.i("[MPINController] clearRetypeFields called"); + logSafe("clearRetypeFields called"); for (final c in retypeControllers) { c.clear(); } @@ -147,7 +145,7 @@ class MPINController extends GetxController { @override void onClose() { - appLogger.i("[MPINController] onClose called"); + logSafe("onClose called"); for (final controller in digitControllers) { controller.dispose(); } @@ -168,7 +166,7 @@ class MPINController extends GetxController { }) async { try { isLoading.value = true; - appLogger.i("[MPINController] generateMPIN started for MPIN: $mpin"); + logSafe("generateMPIN started"); final employeeInfo = LocalStorage.getEmployeeInfo(); final String? employeeId = employeeInfo?.id; @@ -179,8 +177,7 @@ class MPINController extends GetxController { return false; } - appLogger.i( - "[MPINController] Calling AuthService.generateMpin for employeeId: $employeeId"); + logSafe("Calling AuthService.generateMpin for employeeId: $employeeId", sensitive: true); final response = await AuthService.generateMpin( employeeId: employeeId, @@ -190,7 +187,7 @@ class MPINController extends GetxController { isLoading.value = false; if (response == null) { - appLogger.i("[MPINController] MPIN generated successfully"); + logSafe("MPIN generated successfully"); showAppSnackbar( title: "Success", @@ -202,8 +199,7 @@ class MPINController extends GetxController { return true; } else { - appLogger.w( - "[MPINController] MPIN generation returned error response: $response"); + logSafe("MPIN generation returned error: $response", level: LogLevel.warning); showAppSnackbar( title: "MPIN Generation Failed", message: "Please check your inputs.", @@ -216,17 +212,17 @@ class MPINController extends GetxController { } } catch (e) { isLoading.value = false; - _showError("Failed to generate MPIN: $e"); - appLogger.e("[MPINController] Exception in generateMPIN: $e"); + logSafe("Exception in generateMPIN", level: LogLevel.error, error: e); + _showError("Failed to generate MPIN."); return false; } } Future verifyMPIN() async { - appLogger.i("[MPINController] verifyMPIN triggered"); + logSafe("verifyMPIN triggered"); final enteredMPIN = digitControllers.map((c) => c.text).join(); - appLogger.i("[MPINController] Entered MPIN: $enteredMPIN"); + logSafe("Entered MPIN: $enteredMPIN", sensitive: true); if (enteredMPIN.length < 6) { _showError("Please enter all 6 digits."); @@ -251,9 +247,7 @@ class MPINController extends GetxController { isLoading.value = false; if (response == null) { - appLogger.i("[MPINController] MPIN verified successfully."); - - // Set mpin_verified to true in local storage here: + logSafe("MPIN verified successfully"); await LocalStorage.setBool('mpin_verified', true); showAppSnackbar( @@ -264,7 +258,7 @@ class MPINController extends GetxController { _navigateToDashboard(); } else { final errorMessage = response["error"] ?? "Invalid MPIN"; - appLogger.w("[MPINController] MPIN verification failed: $errorMessage"); + logSafe("MPIN verification failed: $errorMessage", level: LogLevel.warning); showAppSnackbar( title: "Error", message: errorMessage, @@ -275,8 +269,7 @@ class MPINController extends GetxController { } } catch (e) { isLoading.value = false; - final error = "Failed to verify MPIN: $e"; - appLogger.e("[MPINController] Exception in verifyMPIN: $error"); + logSafe("Exception in verifyMPIN", level: LogLevel.error, error: e); showAppSnackbar( title: "Error", message: "Something went wrong. Please try again.", diff --git a/lib/controller/auth/otp_controller.dart b/lib/controller/auth/otp_controller.dart index 4cfa29b..4f18c1e 100644 --- a/lib/controller/auth/otp_controller.dart +++ b/lib/controller/auth/otp_controller.dart @@ -4,7 +4,7 @@ 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'; +import 'package:marco/helpers/services/app_logger.dart'; class OTPController extends GetxController { final formKey = GlobalKey(); @@ -25,7 +25,7 @@ class OTPController extends GetxController { void onInit() { super.onInit(); timer.value = 0; - appLogger.i("[OTPController] Initialized"); + logSafe("[OTPController] Initialized"); } @override @@ -38,18 +38,23 @@ class OTPController extends GetxController { for (final node in focusNodes) { node.dispose(); } - appLogger.i("[OTPController] Disposed"); + logSafe("[OTPController] Disposed"); super.onClose(); } Future _sendOTP(String email) async { - appLogger.i("[OTPController] Sending OTP to $email"); + logSafe("[OTPController] Sending OTP"); final result = await AuthService.generateOtp(email); if (result == null) { - appLogger.i("[OTPController] OTP sent successfully to $email"); + logSafe("[OTPController] OTP sent successfully"); return true; } else { - appLogger.w("[OTPController] OTP send failed: ${result['error']}"); + logSafe( + "[OTPController] OTP send failed", + level: LogLevel.warning, + error: result['error'], + + ); showAppSnackbar( title: "Error", message: result['error'] ?? "Failed to send OTP", @@ -61,10 +66,10 @@ class OTPController extends GetxController { Future sendOTP() async { final userEmail = emailController.text.trim(); - appLogger.i("[OTPController] sendOTP called for $userEmail"); + logSafe("[OTPController] sendOTP called"); if (!_validateEmail(userEmail)) { - appLogger.w("[OTPController] Invalid email format: $userEmail"); + logSafe("[OTPController] Invalid email format", level: LogLevel.warning); showAppSnackbar( title: "Error", message: "Please enter a valid email address", @@ -89,7 +94,7 @@ class OTPController extends GetxController { Future onResendOTP() async { if (isResending.value) return; - appLogger.i("[OTPController] Resending OTP to ${email.value}"); + logSafe("[OTPController] Resending OTP"); isResending.value = true; _clearOTPFields(); @@ -103,7 +108,7 @@ class OTPController extends GetxController { } void onOTPChanged(String value, int index) { - appLogger.d("[OTPController] OTP field changed: index=$index, value=$value"); + logSafe("[OTPController] OTP field changed: index=$index", level: LogLevel.debug); if (value.isNotEmpty) { if (index < otpControllers.length - 1) { focusNodes[index + 1].requestFocus(); @@ -119,7 +124,7 @@ class OTPController extends GetxController { Future verifyOTP() async { final enteredOTP = otpControllers.map((c) => c.text).join(); - appLogger.i("[OTPController] Verifying OTP: $enteredOTP for email: ${email.value}"); + logSafe("[OTPController] Verifying OTP"); final result = await AuthService.verifyOtp( email: email.value, @@ -127,23 +132,19 @@ class OTPController extends GetxController { ); if (result == null) { - appLogger.i("[OTPController] OTP verified successfully"); + logSafe("[OTPController] OTP verified successfully"); showAppSnackbar( title: "Success", message: "OTP verified successfully", type: SnackbarType.success, ); final bool isMpinEnabled = LocalStorage.getIsMpin(); - appLogger.i("[OTPController] MPIN Enabled: $isMpinEnabled"); + logSafe("[OTPController] MPIN Enabled: $isMpinEnabled"); - if (isMpinEnabled) { - Get.offAllNamed('/home'); - } else { - Get.offAllNamed('/home'); - } + Get.offAllNamed('/home'); } else { final error = result['error'] ?? "Failed to verify OTP"; - appLogger.w("[OTPController] OTP verification failed: $error"); + logSafe("[OTPController] OTP verification failed", level: LogLevel.warning, error: error, sensitive: true); showAppSnackbar( title: "Error", message: error, @@ -153,7 +154,7 @@ class OTPController extends GetxController { } void _clearOTPFields() { - appLogger.d("[OTPController] Clearing OTP input fields"); + logSafe("[OTPController] Clearing OTP input fields", level: LogLevel.debug); for (final controller in otpControllers) { controller.clear(); } @@ -161,7 +162,7 @@ class OTPController extends GetxController { } void _startTimer() { - appLogger.i("[OTPController] Starting resend timer"); + logSafe("[OTPController] Starting resend timer"); timer.value = 60; _countdownTimer?.cancel(); _countdownTimer = Timer.periodic(const Duration(seconds: 1), (timer) { @@ -174,7 +175,7 @@ class OTPController extends GetxController { } void resetForChangeEmail() { - appLogger.i("[OTPController] Resetting OTP form for change email"); + logSafe("[OTPController] Resetting OTP form for change email"); isOTPSent.value = false; email.value = ''; diff --git a/lib/controller/auth/register_account_controller.dart b/lib/controller/auth/register_account_controller.dart index 836a13d..8236b90 100644 --- a/lib/controller/auth/register_account_controller.dart +++ b/lib/controller/auth/register_account_controller.dart @@ -12,7 +12,7 @@ class RegisterAccountController extends MyController { @override void onInit() { - appLogger.i("[RegisterAccountController] onInit called"); + logSafe("[RegisterAccountController] onInit called"); basicValidator.addField( 'email', @@ -46,32 +46,33 @@ class RegisterAccountController extends MyController { Future onLogin() async { if (basicValidator.validateForm()) { update(); - appLogger.i("[RegisterAccountController] Submitting registration data: ${basicValidator.getData()}"); + final data = basicValidator.getData(); + logSafe("[RegisterAccountController] Submitting registration data"); - var errors = await AuthService.loginUser(basicValidator.getData()); + final errors = await AuthService.loginUser(data); if (errors != null) { - appLogger.w("[RegisterAccountController] Login errors: $errors"); + logSafe("[RegisterAccountController] Login errors: $errors", level: LogLevel.warning); basicValidator.addErrors(errors); basicValidator.validateForm(); basicValidator.clearErrors(); } - appLogger.i("[RegisterAccountController] Redirecting to /starter"); + logSafe("[RegisterAccountController] Redirecting to /starter"); Get.toNamed('/starter'); update(); } else { - appLogger.w("[RegisterAccountController] Validation failed"); + logSafe("[RegisterAccountController] Validation failed", level: LogLevel.warning); } } void onChangeShowPassword() { showPassword = !showPassword; - appLogger.i("[RegisterAccountController] showPassword toggled: $showPassword"); + logSafe("[RegisterAccountController] showPassword toggled: $showPassword"); update(); } void gotoLogin() { - appLogger.i("[RegisterAccountController] Navigating to /auth/login-option"); + logSafe("[RegisterAccountController] Navigating to /auth/login-option"); Get.toNamed('/auth/login-option'); } } diff --git a/lib/controller/auth/reset_password_controller.dart b/lib/controller/auth/reset_password_controller.dart index 983e957..efb1cce 100644 --- a/lib/controller/auth/reset_password_controller.dart +++ b/lib/controller/auth/reset_password_controller.dart @@ -4,7 +4,7 @@ import 'package:marco/controller/my_controller.dart'; import 'package:marco/helpers/services/auth_service.dart'; import 'package:marco/helpers/widgets/my_form_validator.dart'; import 'package:marco/helpers/widgets/my_validators.dart'; -import 'package:marco/helpers/services/app_logger.dart'; +import 'package:marco/helpers/services/app_logger.dart'; class ResetPasswordController extends MyController { MyFormValidator basicValidator = MyFormValidator(); @@ -14,7 +14,7 @@ class ResetPasswordController extends MyController { @override void onInit() { super.onInit(); - appLogger.i("[ResetPasswordController] onInit called"); + logSafe("[ResetPasswordController] onInit called"); basicValidator.addField( 'password', @@ -33,39 +33,39 @@ class ResetPasswordController extends MyController { } Future onResetPassword() async { - appLogger.i("[ResetPasswordController] onResetPassword triggered"); + logSafe("[ResetPasswordController] onResetPassword triggered"); if (basicValidator.validateForm()) { final data = basicValidator.getData(); - appLogger.i("[ResetPasswordController] Form data: $data"); + logSafe("[ResetPasswordController] Reset password form data"); update(); - var errors = await AuthService.loginUser(data); + final errors = await AuthService.loginUser(data); // Consider renaming this to resetPassword() for clarity if (errors != null) { - appLogger.w("[ResetPasswordController] Received errors: $errors"); + logSafe("[ResetPasswordController] Received errors: $errors", level: LogLevel.warning); basicValidator.addErrors(errors); basicValidator.validateForm(); basicValidator.clearErrors(); } - appLogger.i("[ResetPasswordController] Navigating to /home"); + logSafe("[ResetPasswordController] Navigating to /home"); Get.toNamed('/home'); update(); } else { - appLogger.w("[ResetPasswordController] Form validation failed"); + logSafe("[ResetPasswordController] Form validation failed", level: LogLevel.warning); } } void onChangeShowPassword() { showPassword = !showPassword; - appLogger.i("[ResetPasswordController] showPassword toggled: $showPassword"); + logSafe("[ResetPasswordController] showPassword toggled: $showPassword"); update(); } void onConfirmPassword() { confirmPassword = !confirmPassword; - appLogger.i("[ResetPasswordController] confirmPassword toggled: $confirmPassword"); + logSafe("[ResetPasswordController] confirmPassword toggled: $confirmPassword"); update(); } } diff --git a/lib/controller/dashboard/add_employee_controller.dart b/lib/controller/dashboard/add_employee_controller.dart index 1d28c29..250e4cd 100644 --- a/lib/controller/dashboard/add_employee_controller.dart +++ b/lib/controller/dashboard/add_employee_controller.dart @@ -6,7 +6,7 @@ import 'package:marco/helpers/services/api_service.dart'; import 'package:marco/helpers/widgets/my_snackbar.dart'; import 'package:flutter_contacts/flutter_contacts.dart'; import 'package:permission_handler/permission_handler.dart'; -import 'package:marco/helpers/services/app_logger.dart'; +import 'package:marco/helpers/services/app_logger.dart'; enum Gender { male, @@ -16,7 +16,6 @@ enum Gender { const Gender(); } - class AddEmployeeController extends MyController { List files = []; final MyFormValidator basicValidator = MyFormValidator(); @@ -58,15 +57,15 @@ class AddEmployeeController extends MyController { "+33": 9, "+86": 11, }; - String selectedCountryCode = "+91"; + String selectedCountryCode = "+91"; bool showOnline = true; final List categories = []; @override void onInit() { super.onInit(); - appLogger.i("Initializing AddEmployeeController..."); + logSafe("Initializing AddEmployeeController..."); _initializeFields(); fetchRoles(); } @@ -90,41 +89,41 @@ class AddEmployeeController extends MyController { required: true, controller: TextEditingController(), ); - appLogger.i("Fields initialized for first_name, phone_number, last_name."); + logSafe("Fields initialized for first_name, phone_number, last_name."); } void onGenderSelected(Gender? gender) { selectedGender = gender; - appLogger.i("Gender selected: ${gender?.name}"); + logSafe("Gender selected: ${gender?.name}"); update(); } Future fetchRoles() async { - appLogger.i("Fetching roles..."); + logSafe("Fetching roles..."); try { final result = await ApiService.getRoles(); if (result != null) { roles = List>.from(result); - appLogger.i("Roles fetched successfully."); + logSafe("Roles fetched successfully."); update(); } else { - appLogger.e("Failed to fetch roles: null result"); + logSafe("Failed to fetch roles: null result", level: LogLevel.error); } } catch (e, st) { - appLogger.e("Error fetching roles: $e", error: e, stackTrace: st); + logSafe("Error fetching roles", level: LogLevel.error, error: e, stackTrace: st); } } void onRoleSelected(String? roleId) { selectedRoleId = roleId; - appLogger.i("Role selected: $roleId"); + logSafe("Role selected: $roleId"); update(); } Future createEmployees() async { - appLogger.i("Starting employee creation..."); + logSafe("Starting employee creation..."); if (selectedGender == null || selectedRoleId == null) { - appLogger.w("Missing gender or role."); + logSafe("Missing gender or role.", level: LogLevel.warning); showAppSnackbar( title: "Missing Fields", message: "Please select both Gender and Role.", @@ -135,11 +134,9 @@ class AddEmployeeController extends MyController { final firstName = basicValidator.getController("first_name")?.text.trim(); final lastName = basicValidator.getController("last_name")?.text.trim(); - final phoneNumber = - basicValidator.getController("phone_number")?.text.trim(); + final phoneNumber = basicValidator.getController("phone_number")?.text.trim(); - appLogger.i( - "Creating employee with Name: $firstName $lastName, Phone: $phoneNumber, Gender: ${selectedGender!.name}"); + logSafe("Creating employee", level: LogLevel.info); try { final response = await ApiService.createEmployee( @@ -151,7 +148,7 @@ class AddEmployeeController extends MyController { ); if (response == true) { - appLogger.i("Employee created successfully."); + logSafe("Employee created successfully."); showAppSnackbar( title: "Success", message: "Employee created successfully!", @@ -159,10 +156,10 @@ class AddEmployeeController extends MyController { ); return true; } else { - appLogger.e("Failed to create employee (response false)."); + logSafe("Failed to create employee (response false)", level: LogLevel.error); } } catch (e, st) { - appLogger.e("Error creating employee: $e", error: e, stackTrace: st); + logSafe("Error creating employee", level: LogLevel.error, error: e, stackTrace: st); } showAppSnackbar( @@ -176,9 +173,7 @@ class AddEmployeeController extends MyController { Future _checkAndRequestContactsPermission() async { final status = await Permission.contacts.request(); - if (status.isGranted) { - return true; - } + if (status.isGranted) return true; if (status.isPermanentlyDenied) { await openAppSettings(); @@ -186,8 +181,7 @@ class AddEmployeeController extends MyController { showAppSnackbar( title: "Permission Required", - message: - "Please allow Contacts permission from settings to pick a contact.", + message: "Please allow Contacts permission from settings to pick a contact.", type: SnackbarType.warning, ); return false; @@ -195,17 +189,13 @@ class AddEmployeeController extends MyController { Future pickContact(BuildContext context) async { final permissionGranted = await _checkAndRequestContactsPermission(); - if (!permissionGranted) return; try { final picked = await FlutterContacts.openExternalPick(); + if (picked == null) return; - if (picked == null) return; // User canceled contact picking - - final contact = - await FlutterContacts.getContact(picked.id, withProperties: true); - + final contact = await FlutterContacts.getContact(picked.id, withProperties: true); if (contact == null) { showAppSnackbar( title: "Error", @@ -223,10 +213,10 @@ class AddEmployeeController extends MyController { ); return; } + final indiaPhones = contact.phones.where((p) { final normalized = p.number.replaceAll(RegExp(r'[^0-9+]'), ''); - return normalized.startsWith('+91') || - RegExp(r'^\d{10}$').hasMatch(normalized); + return normalized.startsWith('+91') || RegExp(r'^\d{10}$').hasMatch(normalized); }).toList(); if (indiaPhones.isEmpty) { @@ -239,7 +229,6 @@ class AddEmployeeController extends MyController { } String? selectedPhone; - if (indiaPhones.length == 1) { selectedPhone = indiaPhones.first.number; } else { @@ -261,24 +250,16 @@ class AddEmployeeController extends MyController { if (selectedPhone == null) return; } + final normalizedPhone = selectedPhone.replaceAll(RegExp(r'[^0-9]'), ''); + final phoneWithoutCountryCode = normalizedPhone.length > 10 + ? normalizedPhone.substring(normalizedPhone.length - 10) + : normalizedPhone; -// Remove country code prefix if present, keep only 10 digits - String phoneWithoutCountryCode; - - if (normalizedPhone.length > 10) { - phoneWithoutCountryCode = - normalizedPhone.substring(normalizedPhone.length - 10); - } else { - phoneWithoutCountryCode = normalizedPhone; - } - - basicValidator.getController('phone_number')?.text = - phoneWithoutCountryCode; - + basicValidator.getController('phone_number')?.text = phoneWithoutCountryCode; update(); } catch (e, st) { - appLogger.e("Error fetching contacts: $e", error: e, stackTrace: st); + logSafe("Error fetching contacts", level: LogLevel.error, error: e, stackTrace: st); showAppSnackbar( title: "Error", message: "Failed to fetch contacts.", diff --git a/lib/controller/dashboard/attendance_screen_controller.dart b/lib/controller/dashboard/attendance_screen_controller.dart index 3db63ab..45d227d 100644 --- a/lib/controller/dashboard/attendance_screen_controller.dart +++ b/lib/controller/dashboard/attendance_screen_controller.dart @@ -17,7 +17,6 @@ import 'package:marco/model/attendance_log_view_model.dart'; import 'package:marco/controller/project_controller.dart'; class AttendanceController extends GetxController { - // Data lists List attendances = []; List projects = []; List employees = []; @@ -25,14 +24,11 @@ class AttendanceController extends GetxController { List regularizationLogs = []; List attendenceLogsView = []; - // Selected values String selectedTab = 'Employee List'; - // Date range for attendance filtering DateTime? startDateAttendance; DateTime? endDateAttendance; - // Loading states RxBool isLoading = true.obs; RxBool isLoadingProjects = true.obs; RxBool isLoadingEmployees = true.obs; @@ -40,7 +36,6 @@ class AttendanceController extends GetxController { RxBool isLoadingRegularizationLogs = true.obs; RxBool isLoadingLogView = true.obs; - // Uploading state per employee (keyed by employeeId) RxMap uploadingStates = {}.obs; @override @@ -58,47 +53,40 @@ class AttendanceController extends GetxController { final today = DateTime.now(); startDateAttendance = today.subtract(const Duration(days: 7)); endDateAttendance = today.subtract(const Duration(days: 1)); - appLogger.i("Default date range set: $startDateAttendance to $endDateAttendance"); + logSafe("Default date range set: $startDateAttendance to $endDateAttendance"); } - /// Checks and requests location permission, returns true if granted. Future _handleLocationPermission() async { LocationPermission permission = await Geolocator.checkPermission(); - if (permission == LocationPermission.denied) { permission = await Geolocator.requestPermission(); if (permission == LocationPermission.denied) { - appLogger.w('Location permissions are denied'); + logSafe('Location permissions are denied', level: LogLevel.warning); return false; } } - if (permission == LocationPermission.deniedForever) { - appLogger.e('Location permissions are permanently denied'); + logSafe('Location permissions are permanently denied', level: LogLevel.error); return false; } - return true; } - /// Fetches projects and initializes selected project. Future fetchProjects() async { isLoadingProjects.value = true; isLoading.value = true; final response = await ApiService.getProjects(); - if (response != null && response.isNotEmpty) { projects = response.map((json) => ProjectModel.fromJson(json)).toList(); - appLogger.i("Projects fetched: ${projects.length}"); + logSafe("Projects fetched: ${projects.length}"); } else { - appLogger.e("Failed to fetch projects or no projects available."); + logSafe("Failed to fetch projects or no projects available.", level: LogLevel.error); projects = []; } isLoadingProjects.value = false; isLoading.value = false; - update(['attendance_dashboard_controller']); } @@ -109,51 +97,35 @@ class AttendanceController extends GetxController { await fetchProjectData(projectId); } - /// Fetches employees, attendance logs and regularization logs for a project. Future fetchProjectData(String? projectId) async { if (projectId == null) return; - isLoading.value = true; - await Future.wait([ fetchEmployeesByProject(projectId), - fetchAttendanceLogs(projectId, - dateFrom: startDateAttendance, dateTo: endDateAttendance), + fetchAttendanceLogs(projectId, dateFrom: startDateAttendance, dateTo: endDateAttendance), fetchRegularizationLogs(projectId), ]); - isLoading.value = false; - - appLogger.i("Project data fetched for project ID: $projectId"); + logSafe("Project data fetched for project ID: $projectId"); } - /// Fetches employees for the given project. Future fetchEmployeesByProject(String? projectId) async { if (projectId == null) return; - isLoadingEmployees.value = true; - final response = await ApiService.getEmployeesByProject(projectId); - if (response != null) { employees = response.map((json) => EmployeeModel.fromJson(json)).toList(); - - // Initialize uploading states for employees for (var emp in employees) { uploadingStates[emp.id] = false.obs; } - - appLogger.i("Employees fetched: ${employees.length} for project $projectId"); + logSafe("Employees fetched: ${employees.length} for project $projectId"); update(); } else { - appLogger.e("Failed to fetch employees for project $projectId"); + logSafe("Failed to fetch employees for project $projectId", level: LogLevel.error); } - isLoadingEmployees.value = false; } - /// Captures image, gets location, and uploads attendance data. - /// Returns true on success. Future captureAndUploadAttendance( String id, String employeeId, @@ -165,87 +137,52 @@ class AttendanceController extends GetxController { }) async { try { uploadingStates[employeeId]?.value = true; - XFile? image; if (imageCapture) { - image = await ImagePicker().pickImage( - source: ImageSource.camera, - imageQuality: 80, - ); + image = await ImagePicker().pickImage(source: ImageSource.camera, imageQuality: 80); if (image == null) { - appLogger.w("Image capture cancelled."); - uploadingStates[employeeId]?.value = false; + logSafe("Image capture cancelled.", level: LogLevel.warning); return false; } - - final compressedBytes = - await compressImageToUnder100KB(File(image.path)); + final compressedBytes = await compressImageToUnder100KB(File(image.path)); if (compressedBytes == null) { - appLogger.e("Image compression failed."); - uploadingStates[employeeId]?.value = false; + logSafe("Image compression failed.", level: LogLevel.error); return false; } - final compressedFile = await saveCompressedImageToFile(compressedBytes); image = XFile(compressedFile.path); } - final hasLocationPermission = await _handleLocationPermission(); - if (!hasLocationPermission) { - uploadingStates[employeeId]?.value = false; - return false; - } - - final position = await Geolocator.getCurrentPosition( - desiredAccuracy: LocationAccuracy.high, - ); - - final imageName = imageCapture - ? ApiService.generateImageName(employeeId, employees.length + 1) - : ""; + if (!hasLocationPermission) return false; + final position = await Geolocator.getCurrentPosition(desiredAccuracy: LocationAccuracy.high); + final imageName = imageCapture ? ApiService.generateImageName(employeeId, employees.length + 1) : ""; final result = await ApiService.uploadAttendanceImage( - id, - employeeId, - image, - position.latitude, - position.longitude, - imageName: imageName, - projectId: projectId, - comment: comment, - action: action, - imageCapture: imageCapture, - markTime: markTime, + id, employeeId, image, position.latitude, position.longitude, + imageName: imageName, projectId: projectId, comment: comment, + action: action, imageCapture: imageCapture, markTime: markTime, ); - - appLogger.i("Attendance uploaded for $employeeId, action: $action"); + logSafe("Attendance uploaded for $employeeId, action: $action"); return result; } catch (e, stacktrace) { - appLogger.e("Error uploading attendance", error: e, stackTrace: stacktrace); + logSafe("Error uploading attendance", level: LogLevel.error, error: e, stackTrace: stacktrace); return false; } finally { uploadingStates[employeeId]?.value = false; } } - /// Opens a date range picker for attendance filtering and fetches logs on selection. - Future selectDateRangeForAttendance( - BuildContext context, - AttendanceController controller, - ) async { + Future selectDateRangeForAttendance(BuildContext context, AttendanceController controller) async { final today = DateTime.now(); - final todayDateOnly = DateTime(today.year, today.month, today.day); - final picked = await showDateRangePicker( context: context, firstDate: DateTime(2022), - lastDate: todayDateOnly.subtract(const Duration(days: 1)), + lastDate: today.subtract(const Duration(days: 1)), initialDateRange: DateTimeRange( start: startDateAttendance ?? today.subtract(const Duration(days: 7)), - end: endDateAttendance ?? - todayDateOnly.subtract(const Duration(days: 1)), + end: endDateAttendance ?? today.subtract(const Duration(days: 1)), ), - builder: (BuildContext context, Widget? child) { + builder: (context, child) { return Center( child: SizedBox( width: 400, @@ -272,9 +209,7 @@ class AttendanceController extends GetxController { if (picked != null) { startDateAttendance = picked.start; endDateAttendance = picked.end; - - appLogger.i("Date range selected: $startDateAttendance to $endDateAttendance"); - + logSafe("Date range selected: $startDateAttendance to $endDateAttendance"); await controller.fetchAttendanceLogs( Get.find().selectedProject?.id, dateFrom: picked.start, @@ -283,49 +218,31 @@ class AttendanceController extends GetxController { } } - /// Fetches attendance logs filtered by project and date range. - Future fetchAttendanceLogs( - String? projectId, { - DateTime? dateFrom, - DateTime? dateTo, - }) async { + Future fetchAttendanceLogs(String? projectId, {DateTime? dateFrom, DateTime? dateTo}) async { if (projectId == null) return; - isLoadingAttendanceLogs.value = true; isLoading.value = true; - - final response = await ApiService.getAttendanceLogs( - projectId, - dateFrom: dateFrom, - dateTo: dateTo, - ); - + final response = await ApiService.getAttendanceLogs(projectId, dateFrom: dateFrom, dateTo: dateTo); if (response != null) { - attendanceLogs = - response.map((json) => AttendanceLogModel.fromJson(json)).toList(); - appLogger.i("Attendance logs fetched: ${attendanceLogs.length}"); + attendanceLogs = response.map((json) => AttendanceLogModel.fromJson(json)).toList(); + logSafe("Attendance logs fetched: ${attendanceLogs.length}"); update(); } else { - appLogger.e("Failed to fetch attendance logs for project $projectId"); + logSafe("Failed to fetch attendance logs for project $projectId", level: LogLevel.error); } - isLoadingAttendanceLogs.value = false; isLoading.value = false; } - /// Groups attendance logs by check-in date formatted as 'dd MMM yyyy'. Map> groupLogsByCheckInDate() { final groupedLogs = >{}; - for (var logItem in attendanceLogs) { final checkInDate = logItem.checkIn != null ? DateFormat('dd MMM yyyy').format(logItem.checkIn!) : 'Unknown'; - groupedLogs.putIfAbsent(checkInDate, () => []); groupedLogs[checkInDate]!.add(logItem); } - final sortedEntries = groupedLogs.entries.toList() ..sort((a, b) { if (a.key == 'Unknown') return 1; @@ -334,66 +251,43 @@ class AttendanceController extends GetxController { final dateB = DateFormat('dd MMM yyyy').parse(b.key); return dateB.compareTo(dateA); }); - - final sortedMap = - Map>.fromEntries(sortedEntries); - - appLogger.i("Logs grouped and sorted by check-in date."); + final sortedMap = Map>.fromEntries(sortedEntries); + logSafe("Logs grouped and sorted by check-in date."); return sortedMap; } - /// Fetches regularization logs for a project. - Future fetchRegularizationLogs( - String? projectId, { - DateTime? dateFrom, - DateTime? dateTo, - }) async { + Future fetchRegularizationLogs(String? projectId, {DateTime? dateFrom, DateTime? dateTo}) async { if (projectId == null) return; - isLoadingRegularizationLogs.value = true; isLoading.value = true; - final response = await ApiService.getRegularizationLogs(projectId); - if (response != null) { - regularizationLogs = response - .map((json) => RegularizationLogModel.fromJson(json)) - .toList(); - appLogger.i("Regularization logs fetched: ${regularizationLogs.length}"); + regularizationLogs = response.map((json) => RegularizationLogModel.fromJson(json)).toList(); + logSafe("Regularization logs fetched: ${regularizationLogs.length}"); update(); } else { - appLogger.e("Failed to fetch regularization logs for project $projectId"); + logSafe("Failed to fetch regularization logs for project $projectId", level: LogLevel.error); } - isLoadingRegularizationLogs.value = false; isLoading.value = false; } - /// Fetches detailed attendance log view for a specific ID. Future fetchLogsView(String? id) async { if (id == null) return; - isLoadingLogView.value = true; isLoading.value = true; - final response = await ApiService.getAttendanceLogView(id); - if (response != null) { - attendenceLogsView = response - .map((json) => AttendanceLogViewModel.fromJson(json)) - .toList(); - + attendenceLogsView = response.map((json) => AttendanceLogViewModel.fromJson(json)).toList(); attendenceLogsView.sort((a, b) { if (a.activityTime == null || b.activityTime == null) return 0; return b.activityTime!.compareTo(a.activityTime!); }); - - appLogger.i("Attendance log view fetched for ID: $id"); + logSafe("Attendance log view fetched for ID: $id"); update(); } else { - appLogger.e("Failed to fetch attendance log view for ID $id"); + logSafe("Failed to fetch attendance log view for ID $id", level: LogLevel.error); } - isLoadingLogView.value = false; isLoading.value = false; } diff --git a/lib/controller/dashboard/daily_task_controller.dart b/lib/controller/dashboard/daily_task_controller.dart index 31cd2f2..c83292c 100644 --- a/lib/controller/dashboard/daily_task_controller.dart +++ b/lib/controller/dashboard/daily_task_controller.dart @@ -1,6 +1,6 @@ import 'package:flutter/material.dart'; import 'package:get/get.dart'; -import 'package:marco/helpers/services/app_logger.dart'; +import 'package:marco/helpers/services/app_logger.dart'; import 'package:marco/helpers/services/api_service.dart'; import 'package:marco/model/project_model.dart'; import 'package:marco/model/daily_task_model.dart'; @@ -25,6 +25,7 @@ class DailyTaskController extends GetxController { RxBool isLoading = true.obs; Map> groupedDailyTasks = {}; + @override void onInit() { super.onInit(); @@ -39,43 +40,53 @@ class DailyTaskController extends GetxController { final today = DateTime.now(); startDateTask = today.subtract(const Duration(days: 7)); endDateTask = today; - appLogger.i("Default date range set: $startDateTask to $endDateTask"); + + logSafe( + "Default date range set: $startDateTask to $endDateTask", + level: LogLevel.info, + ); } Future fetchTaskData(String? projectId) async { - if (projectId == null) return; + if (projectId == null) { + logSafe("fetchTaskData: Skipped, projectId is null", level: LogLevel.warning); + return; + } isLoading.value = true; + final response = await ApiService.getDailyTasks( projectId, dateFrom: startDateTask, dateTo: endDateTask, ); + isLoading.value = false; if (response != null) { groupedDailyTasks.clear(); for (var taskJson in response) { - TaskModel task = TaskModel.fromJson(taskJson); - String assignmentDateKey = + final task = TaskModel.fromJson(taskJson); + final assignmentDateKey = task.assignmentDate.toIso8601String().split('T')[0]; - if (groupedDailyTasks.containsKey(assignmentDateKey)) { - groupedDailyTasks[assignmentDateKey]?.add(task); - } else { - groupedDailyTasks[assignmentDateKey] = [task]; - } + groupedDailyTasks.putIfAbsent(assignmentDateKey, () => []).add(task); } - // Flatten the grouped tasks into the existing dailyTasks list dailyTasks = groupedDailyTasks.values.expand((list) => list).toList(); - appLogger.i("Daily tasks fetched and grouped: ${dailyTasks.length}"); + logSafe( + "Daily tasks fetched and grouped: ${dailyTasks.length} for project $projectId", + level: LogLevel.info, + ); update(); } else { - appLogger.e("Failed to fetch daily tasks for project $projectId"); + logSafe( + "Failed to fetch daily tasks for project $projectId", + level: LogLevel.error, + ); } } @@ -88,18 +99,23 @@ class DailyTaskController extends GetxController { firstDate: DateTime(2022), lastDate: DateTime.now(), initialDateRange: DateTimeRange( - start: - startDateTask ?? DateTime.now().subtract(const Duration(days: 7)), + start: startDateTask ?? DateTime.now().subtract(const Duration(days: 7)), end: endDateTask ?? DateTime.now(), ), ); - if (picked == null) return; + if (picked == null) { + logSafe("Date range picker cancelled by user.", level: LogLevel.debug); + return; + } startDateTask = picked.start; endDateTask = picked.end; - appLogger.i("Date range selected: $startDateTask to $endDateTask"); + logSafe( + "Date range selected: $startDateTask to $endDateTask", + level: LogLevel.info, + ); await controller.fetchTaskData(controller.selectedProjectId); } diff --git a/lib/controller/dashboard/dashboard_controller.dart b/lib/controller/dashboard/dashboard_controller.dart index 1aaf4b1..b22c0bf 100644 --- a/lib/controller/dashboard/dashboard_controller.dart +++ b/lib/controller/dashboard/dashboard_controller.dart @@ -1,12 +1,11 @@ import 'package:get/get.dart'; -import 'package:marco/helpers/services/app_logger.dart'; +import 'package:marco/helpers/services/app_logger.dart'; import 'package:marco/helpers/services/api_service.dart'; import 'package:marco/controller/project_controller.dart'; class DashboardController extends GetxController { // Observables - final RxList> roleWiseData = - >[].obs; + final RxList> roleWiseData = >[].obs; final RxBool isLoading = false.obs; final RxString selectedRange = '15D'.obs; final RxBool isChartView = true.obs; @@ -14,31 +13,33 @@ class DashboardController extends GetxController { // Inject the ProjectController final ProjectController projectController = Get.find(); - @override -void onInit() { - super.onInit(); + @override + void onInit() { + super.onInit(); - // Log to verify order of controller initialization - appLogger.i('DashboardController initialized with project ID: ${projectController.selectedProjectId.value}'); + logSafe( + 'DashboardController initialized with project ID: ${projectController.selectedProjectId.value}', + level: LogLevel.info, + sensitive: true, + ); - if (projectController.selectedProjectId.value.isNotEmpty) { - fetchRoleWiseAttendance(); - } - - // React to project change - ever(projectController.selectedProjectId, (id) { - if (id.isNotEmpty) { - appLogger.i('Project changed to $id, fetching attendance'); + if (projectController.selectedProjectId.value.isNotEmpty) { fetchRoleWiseAttendance(); } - }); - // React to range change - ever(selectedRange, (_) { - fetchRoleWiseAttendance(); - }); -} + // React to project change + ever(projectController.selectedProjectId, (id) { + if (id.isNotEmpty) { + logSafe('Project changed to $id, fetching attendance', level: LogLevel.info, sensitive: true); + fetchRoleWiseAttendance(); + } + }); + // React to range change + ever(selectedRange, (_) { + fetchRoleWiseAttendance(); + }); + } int get rangeDays => _getDaysFromRange(selectedRange.value); @@ -56,13 +57,16 @@ void onInit() { void updateRange(String range) { selectedRange.value = range; + logSafe('Selected range updated to $range', level: LogLevel.debug); } void toggleChartView(bool isChart) { isChartView.value = isChart; + logSafe('Chart view toggled to: $isChart', level: LogLevel.debug); } Future refreshDashboard() async { + logSafe('Manual dashboard refresh triggered.', level: LogLevel.debug); await fetchRoleWiseAttendance(); } @@ -70,7 +74,7 @@ void onInit() { final String projectId = projectController.selectedProjectId.value; if (projectId.isEmpty) { - appLogger.w('Project ID is empty, skipping API call.'); + logSafe('Project ID is empty, skipping API call.', level: LogLevel.warning); return; } @@ -83,14 +87,19 @@ void onInit() { if (response != null) { roleWiseData.value = response.map((e) => Map.from(e)).toList(); - appLogger.i('Attendance overview fetched successfully.'); + logSafe('Attendance overview fetched successfully.', level: LogLevel.info); } else { - appLogger.e('Failed to fetch attendance overview: response is null.'); roleWiseData.clear(); + logSafe('Failed to fetch attendance overview: response is null.', level: LogLevel.error); } } catch (e, st) { - appLogger.e('Error fetching attendance overview', error: e, stackTrace: st); roleWiseData.clear(); + logSafe( + 'Error fetching attendance overview', + level: LogLevel.error, + error: e, + stackTrace: st, + ); } finally { isLoading.value = false; } diff --git a/lib/controller/dashboard/employees_screen_controller.dart b/lib/controller/dashboard/employees_screen_controller.dart index 5aa9164..ca91a91 100644 --- a/lib/controller/dashboard/employees_screen_controller.dart +++ b/lib/controller/dashboard/employees_screen_controller.dart @@ -1,5 +1,5 @@ import 'package:get/get.dart'; -import 'package:marco/helpers/services/app_logger.dart'; +import 'package:marco/helpers/services/app_logger.dart'; import 'package:marco/helpers/services/api_service.dart'; import 'package:marco/model/attendance_model.dart'; import 'package:marco/model/project_model.dart'; @@ -7,7 +7,6 @@ import 'package:marco/model/employee_model.dart'; import 'package:marco/model/employees/employee_details_model.dart'; import 'package:marco/controller/project_controller.dart'; - class EmployeesScreenController extends GetxController { List attendances = []; List projects = []; @@ -18,9 +17,9 @@ class EmployeesScreenController extends GetxController { RxBool isLoading = false.obs; RxMap uploadingStates = {}.obs; - Rxn selectedEmployeeDetails = - Rxn(); + Rxn selectedEmployeeDetails = Rxn(); RxBool isLoadingEmployeeDetails = false.obs; + @override void onInit() { super.onInit(); @@ -40,68 +39,113 @@ class EmployeesScreenController extends GetxController { Future fetchAllProjects() async { isLoading.value = true; + await _handleApiCall( ApiService.getProjects, onSuccess: (data) { projects = data.map((json) => ProjectModel.fromJson(json)).toList(); - appLogger.i("Projects fetched: ${projects.length} projects loaded."); + logSafe( + "Projects fetched: ${projects.length} projects loaded.", + level: LogLevel.info, + ); + }, + onEmpty: () { + logSafe("No project data found or API call failed.", level: LogLevel.warning); }, - onEmpty: () => appLogger.w("No project data found or API call failed."), ); + isLoading.value = false; update(); } void clearEmployees() { - employees.clear(); // Correct way to clear RxList - appLogger.i("Employees cleared"); + employees.clear(); + logSafe("Employees cleared", level: LogLevel.info); update(['employee_screen_controller']); } Future fetchAllEmployees() async { isLoading.value = true; + await _handleApiCall( ApiService.getAllEmployees, onSuccess: (data) { employees.assignAll(data.map((json) => EmployeeModel.fromJson(json))); - appLogger.i("All Employees fetched: ${employees.length} employees loaded."); + logSafe( + "All Employees fetched: ${employees.length} employees loaded.", + level: LogLevel.info, + ); }, onEmpty: () { - employees.clear(); // Always clear on empty - appLogger.w("No Employee data found or API call failed."); + employees.clear(); + logSafe("No Employee data found or API call failed.", level: LogLevel.warning); }, ); + isLoading.value = false; update(['employee_screen_controller']); } Future fetchEmployeesByProject(String? projectId) async { if (projectId == null || projectId.isEmpty) { - appLogger.e("Project ID is required but was null or empty."); + logSafe("Project ID is required but was null or empty.", level: LogLevel.error); return; } isLoading.value = true; + await _handleApiCall( () => ApiService.getAllEmployeesByProject(projectId), onSuccess: (data) { employees.assignAll(data.map((json) => EmployeeModel.fromJson(json))); + for (var emp in employees) { uploadingStates[emp.id] = false.obs; } - appLogger.i("Employees fetched: ${employees.length} for project $projectId"); + + logSafe( + "Employees fetched: ${employees.length} for project $projectId", + level: LogLevel.info, + sensitive: true, + ); }, onEmpty: () { employees.clear(); - appLogger.w("No employees found for project $projectId."); + logSafe("No employees found for project $projectId.", level: LogLevel.warning, sensitive: true); + }, + onError: (e) { + logSafe("Error fetching employees for project $projectId", level: LogLevel.error, error: e, sensitive: true); }, - onError: (e) => - appLogger.e("Error fetching employees for project $projectId: $e"), ); + isLoading.value = false; update(['employee_screen_controller']); } + Future fetchEmployeeDetails(String? employeeId) async { + if (employeeId == null || employeeId.isEmpty) return; + + isLoadingEmployeeDetails.value = true; + + await _handleSingleApiCall( + () => ApiService.getEmployeeDetails(employeeId), + onSuccess: (data) { + selectedEmployeeDetails.value = EmployeeDetailsModel.fromJson(data); + logSafe("Employee details loaded for $employeeId", level: LogLevel.info, sensitive: true); + }, + onEmpty: () { + selectedEmployeeDetails.value = null; + logSafe("No employee details found for $employeeId", level: LogLevel.warning, sensitive: true); + }, + onError: (e) { + selectedEmployeeDetails.value = null; + logSafe("Error fetching employee details for $employeeId", level: LogLevel.error, error: e, sensitive: true); + }, + ); + + isLoadingEmployeeDetails.value = false; + } + Future _handleApiCall( Future?> Function() apiCall, { required Function(List) onSuccess, @@ -119,32 +163,11 @@ class EmployeesScreenController extends GetxController { if (onError != null) { onError(e); } else { - appLogger.e("API call error: $e"); + logSafe("API call error", level: LogLevel.error, error: e); } } } - Future fetchEmployeeDetails(String? employeeId) async { - if (employeeId == null || employeeId.isEmpty) return; - - isLoadingEmployeeDetails.value = true; - - await _handleSingleApiCall( - () => ApiService.getEmployeeDetails(employeeId), - onSuccess: (data) { - selectedEmployeeDetails.value = EmployeeDetailsModel.fromJson(data); - }, - onEmpty: () { - selectedEmployeeDetails.value = null; - }, - onError: (e) { - selectedEmployeeDetails.value = null; - }, - ); - - isLoadingEmployeeDetails.value = false; - } - Future _handleSingleApiCall( Future?> Function() apiCall, { required Function(Map) onSuccess, @@ -162,7 +185,7 @@ class EmployeesScreenController extends GetxController { if (onError != null) { onError(e); } else { - appLogger.e("API call error: $e"); + logSafe("API call error", level: LogLevel.error, error: e); } } } diff --git a/lib/controller/layout/layout_controller.dart b/lib/controller/layout/layout_controller.dart index 0ba9ffc..1ee06ea 100644 --- a/lib/controller/layout/layout_controller.dart +++ b/lib/controller/layout/layout_controller.dart @@ -1,12 +1,10 @@ import 'package:flutter/material.dart'; import 'package:get/get.dart'; -import 'package:marco/helpers/services/app_logger.dart'; +import 'package:marco/helpers/services/app_logger.dart'; import 'package:marco/helpers/services/api_service.dart'; import 'package:marco/helpers/theme/theme_customizer.dart'; import 'package:marco/model/project_model.dart'; - - class LayoutController extends GetxController { // Theme Customization ThemeCustomizer themeCustomizer = ThemeCustomizer(); @@ -51,20 +49,25 @@ class LayoutController extends GetxController { super.dispose(); } - // Project Handling + /// Fetch project list from API and initialize the selection. Future fetchProjects() async { isLoading.value = true; isLoadingProjects.value = true; - final response = await ApiService.getProjects(); + try { + final response = await ApiService.getProjects(); - if (response != null && response.isNotEmpty) { - final fetchedProjects = response.map((json) => ProjectModel.fromJson(json)).toList(); - projects.assignAll(fetchedProjects); - selectedProjectId = RxString(fetchedProjects.first.id.toString()); - appLogger.i("Projects fetched: ${fetchedProjects.length}"); - } else { - appLogger.w("No projects found or API call failed."); + if (response != null && response.isNotEmpty) { + final fetchedProjects = response.map((json) => ProjectModel.fromJson(json)).toList(); + projects.assignAll(fetchedProjects); + selectedProjectId = RxString(fetchedProjects.first.id.toString()); + + logSafe("Projects fetched: ${fetchedProjects.length}", level: LogLevel.info); + } else { + logSafe("No projects found or API call failed.", level: LogLevel.warning); + } + } catch (e, st) { + logSafe("Error fetching projects", level: LogLevel.error, error: e, stackTrace: st); } isLoadingProjects.value = false; @@ -72,32 +75,38 @@ class LayoutController extends GetxController { update(['dashboard_controller']); } + /// Update selected project ID void updateSelectedProject(String projectId) { selectedProjectId?.value = projectId; + logSafe("Selected project updated", level: LogLevel.info); } + /// Toggle expansion of the project list section void toggleProjectListExpanded() { isProjectListExpanded.toggle(); + logSafe("Project list expanded: ${isProjectListExpanded.value}", level: LogLevel.debug); } - // Theme Updates + /// Handle theme changes (light/dark, drawer toggles) void onChangeTheme(ThemeCustomizer oldVal, ThemeCustomizer newVal) { themeCustomizer = newVal; update(); if (newVal.rightBarOpen) { scaffoldKey.currentState?.openEndDrawer(); + logSafe("Theme changed — end drawer opened", level: LogLevel.debug); } else { scaffoldKey.currentState?.closeEndDrawer(); + logSafe("Theme changed — end drawer closed", level: LogLevel.debug); } } - // Notification Shade (placeholders) + /// Optional notification toggles (placeholder) void enableNotificationShade() { - // Add implementation if needed + logSafe("Notification shade enabled (not implemented)", level: LogLevel.verbose); } void disableNotificationShade() { - // Add implementation if needed + logSafe("Notification shade disabled (not implemented)", level: LogLevel.verbose); } } diff --git a/lib/controller/permission_controller.dart b/lib/controller/permission_controller.dart index d2b0235..1eb55d5 100644 --- a/lib/controller/permission_controller.dart +++ b/lib/controller/permission_controller.dart @@ -3,14 +3,11 @@ import 'dart:convert'; import 'package:get/get.dart'; import 'package:shared_preferences/shared_preferences.dart'; import 'package:marco/helpers/services/app_logger.dart'; - import 'package:marco/helpers/services/permission_service.dart'; import 'package:marco/model/user_permission.dart'; import 'package:marco/model/employee_info.dart'; import 'package:marco/model/projects_model.dart'; - - class PermissionController extends GetxController { var permissions = [].obs; var employeeInfo = Rxn(); @@ -47,9 +44,9 @@ class PermissionController extends GetxController { ); } - appLogger.i("User data successfully stored in SharedPreferences."); + logSafe("User data successfully stored in SharedPreferences."); } catch (e, stacktrace) { - appLogger.e("Error storing data", error: e, stackTrace: stacktrace); + logSafe("Error storing data", level: LogLevel.error, error: e, stackTrace: stacktrace); } } @@ -58,7 +55,7 @@ class PermissionController extends GetxController { if (token?.isNotEmpty ?? false) { await loadData(token!); } else { - appLogger.w("No token found for loading API data."); + logSafe("No token found for loading API data.", level: LogLevel.warning); } } @@ -67,9 +64,9 @@ class PermissionController extends GetxController { final userData = await PermissionService.fetchAllUserData(token); _updateState(userData); await _storeData(); - appLogger.i("Data loaded and state updated successfully."); + logSafe("Data loaded and state updated successfully."); } catch (e, stacktrace) { - appLogger.e("Error loading data from API", error: e, stackTrace: stacktrace); + logSafe("Error loading data from API", level: LogLevel.error, error: e, stackTrace: stacktrace); } } @@ -78,45 +75,48 @@ class PermissionController extends GetxController { permissions.assignAll(userData['permissions']); employeeInfo.value = userData['employeeInfo']; projectsInfo.assignAll(userData['projects']); - appLogger.i("State updated with new user data."); + + logSafe("State updated with new user data.", sensitive: true); } catch (e, stacktrace) { - appLogger.e("Error updating state", error: e, stackTrace: stacktrace); + logSafe("Error updating state", level: LogLevel.error, error: e, stackTrace: stacktrace); } } Future _getAuthToken() async { try { final prefs = await SharedPreferences.getInstance(); - return prefs.getString('jwt_token'); + final token = prefs.getString('jwt_token'); + logSafe("Auth token retrieved successfully.", sensitive: true); + return token; } catch (e, stacktrace) { - appLogger.e("Error retrieving auth token", error: e, stackTrace: stacktrace); + logSafe("Error retrieving auth token", level: LogLevel.error, error: e, stackTrace: stacktrace); return null; } } void _startAutoRefresh() { _refreshTimer = Timer.periodic(Duration(minutes: 30), (timer) async { - appLogger.i("Auto-refresh triggered."); + logSafe("Auto-refresh triggered."); await _loadDataFromAPI(); }); } bool hasPermission(String permissionId) { final hasPerm = permissions.any((p) => p.id == permissionId); - appLogger.d("Checking permission $permissionId: $hasPerm"); + logSafe("Checking permission $permissionId: $hasPerm", level: LogLevel.debug); return hasPerm; } bool isUserAssignedToProject(String projectId) { final assigned = projectsInfo.any((project) => project.id == projectId); - appLogger.d("Checking project assignment for $projectId: $assigned"); + logSafe("Checking project assignment for $projectId: $assigned", level: LogLevel.debug); return assigned; } @override void onClose() { _refreshTimer?.cancel(); - appLogger.i("PermissionController disposed and timer cancelled."); + logSafe("PermissionController disposed and timer cancelled."); super.onClose(); } } diff --git a/lib/controller/project_controller.dart b/lib/controller/project_controller.dart index a2b1a73..6fed738 100644 --- a/lib/controller/project_controller.dart +++ b/lib/controller/project_controller.dart @@ -14,9 +14,9 @@ class ProjectController extends GetxController { RxBool isLoading = true.obs; RxBool isLoadingProjects = true.obs; RxMap uploadingStates = {}.obs; + GlobalProjectModel? get selectedProject { if (selectedProjectId.value.isEmpty) return null; - return projects.firstWhereOrNull((p) => p.id == selectedProjectId.value); } @@ -36,7 +36,10 @@ class ProjectController extends GetxController { isLoadingProjects.value = false; isLoading.value = false; uploadingStates.clear(); + LocalStorage.saveString('selectedProjectId', ''); + + logSafe("Projects cleared and UI states reset."); update(); } @@ -49,20 +52,21 @@ class ProjectController extends GetxController { if (response != null && response.isNotEmpty) { projects.assignAll( - response.map((json) => GlobalProjectModel.fromJson(json)).toList()); + response.map((json) => GlobalProjectModel.fromJson(json)).toList(), + ); String? savedId = LocalStorage.getString('selectedProjectId'); if (savedId != null && projects.any((p) => p.id == savedId)) { - selectedProjectId.value = savedId; // ✅ update value only + selectedProjectId.value = savedId; } else { - selectedProjectId.value = projects.first.id.toString(); // ✅ + selectedProjectId.value = projects.first.id.toString(); LocalStorage.saveString('selectedProjectId', selectedProjectId.value); } isProjectSelectionExpanded.value = false; - appLogger.i("Projects fetched: ${projects.length}"); + logSafe("Projects fetched: ${projects.length}"); } else { - appLogger.w("No projects found or API call failed."); + logSafe("No projects found or API call failed.", level: LogLevel.warning); } isLoadingProjects.value = false; @@ -72,8 +76,8 @@ class ProjectController extends GetxController { Future updateSelectedProject(String projectId) async { selectedProjectId.value = projectId; - await LocalStorage.saveString('selectedProjectId', projectId); + logSafe("Selected project updated to $projectId"); update(['selected_project']); } } diff --git a/lib/controller/task_planing/add_task_controller.dart b/lib/controller/task_planing/add_task_controller.dart index 8cd37f3..4e644ae 100644 --- a/lib/controller/task_planing/add_task_controller.dart +++ b/lib/controller/task_planing/add_task_controller.dart @@ -1,11 +1,10 @@ import 'package:get/get.dart'; -import 'package:marco/helpers/services/app_logger.dart'; +import 'package:marco/helpers/services/app_logger.dart'; import 'package:marco/helpers/services/api_service.dart'; import 'package:marco/helpers/widgets/my_form_validator.dart'; import 'package:marco/helpers/widgets/my_snackbar.dart'; import 'package:marco/model/dailyTaskPlaning/master_work_category_model.dart'; - class AddTaskController extends GetxController { RxMap uploadingStates = {}.obs; MyFormValidator basicValidator = MyFormValidator(); @@ -19,6 +18,7 @@ class AddTaskController extends GetxController { RxList workMasterCategories = [].obs; RxBool isLoading = false.obs; + @override void onInit() { super.onInit(); @@ -29,15 +29,11 @@ class AddTaskController extends GetxController { if (value == null || value.trim().isEmpty) { return 'This field is required'; } - if (fieldType == "target") { - if (int.tryParse(value.trim()) == null) { - return 'Please enter a valid number'; - } + if (fieldType == "target" && int.tryParse(value.trim()) == null) { + return 'Please enter a valid number'; } - if (fieldType == "description") { - if (value.trim().length < 5) { - return 'Description must be at least 5 characters'; - } + if (fieldType == "description" && value.trim().length < 5) { + return 'Description must be at least 5 characters'; } return null; } @@ -49,7 +45,7 @@ class AddTaskController extends GetxController { required List taskTeam, DateTime? assignmentDate, }) async { - appLogger.i("Starting assign task..."); + logSafe("Starting task assignment...", level: LogLevel.info); final response = await ApiService.assignDailyTask( workItemId: workItemId, @@ -60,7 +56,7 @@ class AddTaskController extends GetxController { ); if (response == true) { - appLogger.i("Task assigned successfully."); + logSafe("Task assigned successfully.", level: LogLevel.info); showAppSnackbar( title: "Success", message: "Task assigned successfully!", @@ -68,7 +64,7 @@ class AddTaskController extends GetxController { ); return true; } else { - appLogger.e("Failed to assign task."); + logSafe("Failed to assign task.", level: LogLevel.error); showAppSnackbar( title: "Error", message: "Failed to assign task.", @@ -87,7 +83,7 @@ class AddTaskController extends GetxController { required String categoryId, DateTime? assignmentDate, }) async { - appLogger.i("Creating new task..."); + logSafe("Creating new task...", level: LogLevel.info); final response = await ApiService.createTask( parentTaskId: parentTaskId, @@ -97,11 +93,10 @@ class AddTaskController extends GetxController { activityId: activityId, assignmentDate: assignmentDate, categoryId: categoryId, - ); if (response == true) { - appLogger.i("Task created successfully."); + logSafe("Task created successfully.", level: LogLevel.info); showAppSnackbar( title: "Success", message: "Task created successfully!", @@ -109,7 +104,7 @@ class AddTaskController extends GetxController { ); return true; } else { - appLogger.e("Failed to create task."); + logSafe("Failed to create task.", level: LogLevel.error); showAppSnackbar( title: "Error", message: "Failed to create task.", @@ -122,9 +117,9 @@ class AddTaskController extends GetxController { Future fetchWorkMasterCategories() async { isLoadingWorkMasterCategories.value = true; - final response = await ApiService.getMasterWorkCategories(); - if (response != null) { - try { + try { + final response = await ApiService.getMasterWorkCategories(); + if (response != null) { final dataList = response['data'] ?? []; final parsedList = List.from( @@ -132,19 +127,17 @@ class AddTaskController extends GetxController { ); workMasterCategories.assignAll(parsedList); - final Map mapped = { - for (var item in parsedList) item.id: item.name, - }; + final mapped = {for (var item in parsedList) item.id: item.name}; categoryIdNameMap.assignAll(mapped); - appLogger.i("Work categories fetched: ${dataList.length}"); - } catch (e) { - appLogger.e("Error parsing work categories: $e"); - workMasterCategories.clear(); - categoryIdNameMap.clear(); + logSafe("Work categories fetched: ${dataList.length}", level: LogLevel.info); + } else { + logSafe("No work categories found or API call failed.", level: LogLevel.warning); } - } else { - appLogger.w("No work categories found or API call failed."); + } catch (e, st) { + logSafe("Error parsing work categories", level: LogLevel.error, error: e, stackTrace: st); + workMasterCategories.clear(); + categoryIdNameMap.clear(); } isLoadingWorkMasterCategories.value = false; @@ -154,5 +147,6 @@ class AddTaskController extends GetxController { void selectCategory(String id) { selectedCategoryId.value = id; selectedCategoryName.value = categoryIdNameMap[id]; + logSafe("Category selected", level: LogLevel.debug, sensitive: true); } } diff --git a/lib/controller/task_planing/daily_task_planing_controller.dart b/lib/controller/task_planing/daily_task_planing_controller.dart index 3293200..32b7e59 100644 --- a/lib/controller/task_planing/daily_task_planing_controller.dart +++ b/lib/controller/task_planing/daily_task_planing_controller.dart @@ -1,31 +1,26 @@ import 'package:get/get.dart'; -import 'package:marco/helpers/services/app_logger.dart'; +import 'package:marco/helpers/services/app_logger.dart'; import 'package:marco/helpers/services/api_service.dart'; +import 'package:marco/helpers/widgets/my_form_validator.dart'; +import 'package:marco/helpers/widgets/my_snackbar.dart'; import 'package:marco/model/project_model.dart'; import 'package:marco/model/dailyTaskPlaning/daily_task_planing_model.dart'; import 'package:marco/model/employee_model.dart'; -import 'package:marco/helpers/widgets/my_form_validator.dart'; -import 'package:marco/helpers/widgets/my_snackbar.dart'; - class DailyTaskPlaningController extends GetxController { List projects = []; List employees = []; List dailyTasks = []; - RxMap uploadingStates = {}.obs; - MyFormValidator basicValidator = MyFormValidator(); - List> roles = []; - RxnString selectedRoleId = RxnString(); + RxMap uploadingStates = {}.obs; RxList selectedEmployees = [].obs; - void updateSelectedEmployees() { - final selected = - employees.where((e) => uploadingStates[e.id]?.value == true).toList(); - selectedEmployees.value = selected; - } + MyFormValidator basicValidator = MyFormValidator(); + List> roles = []; + RxnString selectedRoleId = RxnString(); RxBool isLoading = false.obs; + @override void onInit() { super.onInit(); @@ -41,114 +36,113 @@ class DailyTaskPlaningController extends GetxController { if (value == null || value.trim().isEmpty) { return 'This field is required'; } - if (fieldType == "target") { - if (int.tryParse(value.trim()) == null) { - return 'Please enter a valid number'; - } + if (fieldType == "target" && int.tryParse(value.trim()) == null) { + return 'Please enter a valid number'; } - if (fieldType == "description") { - if (value.trim().length < 5) { - return 'Description must be at least 5 characters'; - } + if (fieldType == "description" && value.trim().length < 5) { + return 'Description must be at least 5 characters'; } return null; } - Future fetchRoles() async { - appLogger.i("Fetching roles..."); - final result = await ApiService.getRoles(); - if (result != null) { - roles = List>.from(result); - appLogger.i("Roles fetched successfully."); - update(); - } else { - appLogger.e("Failed to fetch roles."); - } + void updateSelectedEmployees() { + final selected = employees + .where((e) => uploadingStates[e.id]?.value == true) + .toList(); + selectedEmployees.value = selected; + logSafe("Updated selected employees", level: LogLevel.debug, sensitive: true); } void onRoleSelected(String? roleId) { selectedRoleId.value = roleId; - appLogger.i("Role selected: $roleId"); + logSafe("Role selected", level: LogLevel.info, sensitive: true); + } + + Future fetchRoles() async { + logSafe("Fetching roles...", level: LogLevel.info); + final result = await ApiService.getRoles(); + if (result != null) { + roles = List>.from(result); + logSafe("Roles fetched successfully", level: LogLevel.info); + update(); + } else { + logSafe("Failed to fetch roles", level: LogLevel.error); + } } Future assignDailyTask({ - required String workItemId, - required int plannedTask, - required String description, - required List taskTeam, - DateTime? assignmentDate, -}) async { - appLogger.i("Starting assign task..."); + required String workItemId, + required int plannedTask, + required String description, + required List taskTeam, + DateTime? assignmentDate, + }) async { + logSafe("Starting assign task...", level: LogLevel.info); - final response = await ApiService.assignDailyTask( - workItemId: workItemId, - plannedTask: plannedTask, - description: description, - taskTeam: taskTeam, - assignmentDate: assignmentDate, - ); + final response = await ApiService.assignDailyTask( + workItemId: workItemId, + plannedTask: plannedTask, + description: description, + taskTeam: taskTeam, + assignmentDate: assignmentDate, + ); - if (response == true) { - appLogger.i("Task assigned successfully."); - showAppSnackbar( - title: "Success", - message: "Task assigned successfully!", - type: SnackbarType.success, - ); - return true; - } else { - appLogger.e("Failed to assign task."); - showAppSnackbar( - title: "Error", - message: "Failed to assign task.", - type: SnackbarType.error, - ); - return false; + if (response == true) { + logSafe("Task assigned successfully", level: LogLevel.info); + showAppSnackbar( + title: "Success", + message: "Task assigned successfully!", + type: SnackbarType.success, + ); + return true; + } else { + logSafe("Failed to assign task", level: LogLevel.error); + showAppSnackbar( + title: "Error", + message: "Failed to assign task.", + type: SnackbarType.error, + ); + return false; + } } -} - Future fetchProjects() async { + isLoading.value = true; try { - isLoading.value = true; - final response = await ApiService.getProjects(); if (response?.isEmpty ?? true) { - appLogger.w("No project data found or API call failed."); + logSafe("No project data found or API call failed", level: LogLevel.warning); return; } projects = response!.map((json) => ProjectModel.fromJson(json)).toList(); - appLogger.i("Projects fetched: ${projects.length} projects loaded."); + logSafe("Projects fetched: ${projects.length} projects loaded", level: LogLevel.info); update(); } catch (e, stack) { - appLogger.e("Error fetching projects", error: e, stackTrace: stack); + logSafe("Error fetching projects", level: LogLevel.error, error: e, stackTrace: stack); } finally { isLoading.value = false; } } Future fetchTaskData(String? projectId) async { - if (projectId == null) return; + if (projectId == null) { + logSafe("Project ID is null", level: LogLevel.warning); + return; + } + isLoading.value = true; try { - isLoading.value = true; - final response = await ApiService.getDailyTasksDetails(projectId); - if (response != null) { - final data = response['data']; - if (data != null) { - dailyTasks = [TaskPlanningDetailsModel.fromJson(data)]; - appLogger.i("Daily task Planning Details fetched."); - } else { - appLogger.e("Data field is null"); - } + final data = response?['data']; + if (data != null) { + dailyTasks = [TaskPlanningDetailsModel.fromJson(data)]; + logSafe("Daily task Planning Details fetched", level: LogLevel.info, sensitive: true); } else { - appLogger.e( - "Failed to fetch daily task planning Details for project $projectId"); + logSafe("Data field is null", level: LogLevel.warning); } } catch (e, stack) { - appLogger.e("Error fetching daily task data", error: e, stackTrace: stack); + logSafe("Error fetching daily task data", level: LogLevel.error, error: e, stackTrace: stack); } finally { isLoading.value = false; update(); @@ -157,7 +151,7 @@ class DailyTaskPlaningController extends GetxController { Future fetchEmployeesByProject(String? projectId) async { if (projectId == null || projectId.isEmpty) { - appLogger.e("Project ID is required but was null or empty."); + logSafe("Project ID is required but was null or empty", level: LogLevel.error); return; } @@ -165,21 +159,22 @@ class DailyTaskPlaningController extends GetxController { try { final response = await ApiService.getAllEmployeesByProject(projectId); if (response != null && response.isNotEmpty) { - employees = - response.map((json) => EmployeeModel.fromJson(json)).toList(); + employees = response.map((json) => EmployeeModel.fromJson(json)).toList(); for (var emp in employees) { uploadingStates[emp.id] = false.obs; } - appLogger.i("Employees fetched: ${employees.length} for project $projectId"); + logSafe("Employees fetched: ${employees.length} for project $projectId", + level: LogLevel.info, sensitive: true); } else { - appLogger.w("No employees found for project $projectId."); employees = []; + logSafe("No employees found for project $projectId", level: LogLevel.warning, sensitive: true); } - } catch (e) { - appLogger.e("Error fetching employees for project $projectId: $e"); + } catch (e, stack) { + logSafe("Error fetching employees for project $projectId", + level: LogLevel.error, error: e, stackTrace: stack, sensitive: true); + } finally { + isLoading.value = false; + update(); } - - update(); - isLoading.value = false; } } diff --git a/lib/controller/task_planing/report_task_action_controller.dart b/lib/controller/task_planing/report_task_action_controller.dart index 798983f..96e782d 100644 --- a/lib/controller/task_planing/report_task_action_controller.dart +++ b/lib/controller/task_planing/report_task_action_controller.dart @@ -4,7 +4,7 @@ import 'dart:io'; import 'package:flutter/material.dart'; import 'package:get/get.dart'; import 'package:image_picker/image_picker.dart'; -import 'package:marco/helpers/services/app_logger.dart'; +import 'package:marco/helpers/services/app_logger.dart'; import 'package:marco/controller/my_controller.dart'; import 'package:marco/controller/task_planing/daily_task_planing_controller.dart'; @@ -14,13 +14,9 @@ import 'package:marco/helpers/widgets/my_image_compressor.dart'; import 'package:marco/helpers/widgets/my_snackbar.dart'; import 'package:marco/model/dailyTaskPlaning/work_status_model.dart'; - enum ApiStatus { idle, loading, success, failure } class ReportTaskActionController extends MyController { - // ──────────────────────────────────────────────── - // Reactive State - // ──────────────────────────────────────────────── final RxBool isLoading = false.obs; final Rx reportStatus = ApiStatus.idle.obs; final Rx commentStatus = ApiStatus.idle.obs; @@ -37,9 +33,6 @@ class ReportTaskActionController extends MyController { final RxString selectedWorkStatusName = ''.obs; - // ──────────────────────────────────────────────── - // Controllers & Validators - // ──────────────────────────────────────────────── final MyFormValidator basicValidator = MyFormValidator(); final DailyTaskPlaningController taskController = Get.put(DailyTaskPlaningController()); final ImagePicker _picker = ImagePicker(); @@ -72,13 +65,10 @@ class ReportTaskActionController extends MyController { approvedTaskController, ]; - // ──────────────────────────────────────────────── - // Lifecycle Hooks - // ──────────────────────────────────────────────── @override void onInit() { super.onInit(); - appLogger.i("Initializing ReportTaskController..."); + logSafe("Initializing ReportTaskController..."); _initializeFormFields(); } @@ -87,12 +77,10 @@ class ReportTaskActionController extends MyController { for (final controller in _allControllers) { controller.dispose(); } + logSafe("Disposed all text controllers in ReportTaskActionController."); super.onClose(); } - // ──────────────────────────────────────────────── - // Form Field Setup - // ──────────────────────────────────────────────── void _initializeFormFields() { basicValidator ..addField('assigned_date', label: "Assigned Date", controller: assignedDateController) @@ -109,9 +97,6 @@ class ReportTaskActionController extends MyController { ..addField('approved_task', label: "Approved Task", required: true, controller: approvedTaskController); } - // ──────────────────────────────────────────────── - // Task Approval Logic - // ──────────────────────────────────────────────── Future approveTask({ required String projectId, required String comment, @@ -119,14 +104,11 @@ class ReportTaskActionController extends MyController { required String approvedTaskCount, List? images, }) async { - appLogger.i("Starting task approval..."); - appLogger.i("Project ID: $projectId"); - appLogger.i("Comment: $comment"); - appLogger.i("Report Action ID: $reportActionId"); - appLogger.i("Approved Task Count: $approvedTaskCount"); + logSafe("approveTask() started", sensitive: false); if (projectId.isEmpty || reportActionId.isEmpty) { _showError("Project ID and Report Action ID are required."); + logSafe("Missing required projectId or reportActionId", level: LogLevel.warning); return false; } @@ -135,25 +117,29 @@ class ReportTaskActionController extends MyController { if (approvedTaskInt == null) { _showError("Invalid approved task count."); + logSafe("Invalid approvedTaskCount: $approvedTaskCount", level: LogLevel.warning); return false; } if (completedWorkInt != null && approvedTaskInt > completedWorkInt) { _showError("Approved task count cannot exceed completed work."); + logSafe("Validation failed: approved > completed", level: LogLevel.warning); return false; } if (comment.trim().isEmpty) { _showError("Comment is required."); + logSafe("Comment field is empty", level: LogLevel.warning); return false; } try { reportStatus.value = ApiStatus.loading; isLoading.value = true; - + logSafe("Calling _prepareImages() for approval..."); final imageData = await _prepareImages(images); + logSafe("Calling ApiService.approveTask()"); final success = await ApiService.approveTask( id: projectId, workStatus: reportActionId, @@ -163,15 +149,17 @@ class ReportTaskActionController extends MyController { ); if (success) { + logSafe("Task approved successfully"); _showSuccess("Task approved successfully!"); await taskController.fetchTaskData(projectId); return true; } else { + logSafe("API returned failure on approveTask", level: LogLevel.error); _showError("Failed to approve task."); return false; } - } catch (e) { - appLogger.e("Error approving task: $e"); + } catch (e, st) { + logSafe("Error in approveTask: $e", level: LogLevel.error, error: e, stackTrace: st); _showError("An error occurred."); return false; } finally { @@ -182,26 +170,26 @@ class ReportTaskActionController extends MyController { } } - // ──────────────────────────────────────────────── - // Comment Task Logic - // ──────────────────────────────────────────────── Future commentTask({ required String projectId, required String comment, List? images, }) async { - appLogger.i("Starting task comment..."); + logSafe("commentTask() started", sensitive: false); if (commentController.text.trim().isEmpty) { _showError("Comment is required."); + logSafe("Comment field is empty", level: LogLevel.warning); return; } try { isLoading.value = true; + logSafe("Calling _prepareImages() for comment..."); final imageData = await _prepareImages(images); + logSafe("Calling ApiService.commentTask()"); final success = await ApiService.commentTask( id: projectId, comment: commentController.text.trim(), @@ -211,31 +199,32 @@ class ReportTaskActionController extends MyController { }); if (success) { + logSafe("Comment added successfully"); _showSuccess("Task commented successfully!"); await taskController.fetchTaskData(projectId); } else { + logSafe("API returned failure on commentTask", level: LogLevel.error); _showError("Failed to comment task."); } - } catch (e) { - appLogger.e("Error commenting task: $e"); + } catch (e, st) { + logSafe("Error in commentTask: $e", level: LogLevel.error, error: e, stackTrace: st); _showError("An error occurred while commenting the task."); } finally { isLoading.value = false; } } - // ──────────────────────────────────────────────── - // API Helpers - // ──────────────────────────────────────────────── Future fetchWorkStatuses() async { + logSafe("Fetching work statuses..."); isLoadingWorkStatus.value = true; final response = await ApiService.getWorkStatus(); if (response != null) { final model = WorkStatusResponseModel.fromJson(response); workStatus.assignAll(model.data); + logSafe("Fetched ${model.data.length} work statuses"); } else { - appLogger.w("No work statuses found or API call failed."); + logSafe("No work statuses found or API call failed", level: LogLevel.warning); } isLoadingWorkStatus.value = false; @@ -243,8 +232,12 @@ class ReportTaskActionController extends MyController { } Future>?> _prepareImages(List? images) async { - if (images == null || images.isEmpty) return null; + if (images == null || images.isEmpty) { + logSafe("_prepareImages: No images selected."); + return null; + } + logSafe("_prepareImages: Compressing and encoding images..."); final results = await Future.wait(images.map((file) async { final compressedBytes = await compressImageToUnder100KB(file); if (compressedBytes == null) return null; @@ -258,6 +251,7 @@ class ReportTaskActionController extends MyController { }; })); + logSafe("_prepareImages: Prepared ${results.whereType>().length} images."); return results.whereType>().toList(); } @@ -272,33 +266,28 @@ class ReportTaskActionController extends MyController { }; } - // ──────────────────────────────────────────────── - // Image Picker Utils - // ──────────────────────────────────────────────── Future pickImages({required bool fromCamera}) async { + logSafe("Opening image picker..."); if (fromCamera) { - final pickedFile = await _picker.pickImage( - source: ImageSource.camera, - imageQuality: 75, - ); + final pickedFile = await _picker.pickImage(source: ImageSource.camera, imageQuality: 75); if (pickedFile != null) { selectedImages.add(File(pickedFile.path)); + logSafe("Image added from camera: ${pickedFile.path}", sensitive: true); } } else { final pickedFiles = await _picker.pickMultiImage(imageQuality: 75); selectedImages.addAll(pickedFiles.map((xfile) => File(xfile.path))); + logSafe("${pickedFiles.length} images added from gallery.", sensitive: true); } } void removeImageAt(int index) { if (index >= 0 && index < selectedImages.length) { + logSafe("Removing image at index $index", sensitive: true); selectedImages.removeAt(index); } } - // ──────────────────────────────────────────────── - // Snackbar Feedback - // ──────────────────────────────────────────────── void _showError(String message) => showAppSnackbar( title: "Error", message: message, type: SnackbarType.error); diff --git a/lib/controller/task_planing/report_task_controller.dart b/lib/controller/task_planing/report_task_controller.dart index 0e1d372..35077c7 100644 --- a/lib/controller/task_planing/report_task_controller.dart +++ b/lib/controller/task_planing/report_task_controller.dart @@ -4,7 +4,7 @@ import 'package:marco/controller/my_controller.dart'; import 'package:marco/helpers/widgets/my_form_validator.dart'; import 'package:marco/helpers/services/api_service.dart'; import 'package:get/get.dart'; -import 'package:marco/helpers/services/app_logger.dart'; +import 'package:marco/helpers/services/app_logger.dart'; import 'package:marco/helpers/widgets/my_snackbar.dart'; import 'package:marco/controller/task_planing/daily_task_planing_controller.dart'; import 'package:image_picker/image_picker.dart'; @@ -12,11 +12,9 @@ import 'dart:io'; import 'dart:convert'; import 'package:marco/helpers/widgets/my_image_compressor.dart'; - enum ApiStatus { idle, loading, success, failure } -final DailyTaskPlaningController taskController = - Get.put(DailyTaskPlaningController()); +final DailyTaskPlaningController taskController = Get.put(DailyTaskPlaningController()); final ImagePicker _picker = ImagePicker(); class ReportTaskController extends MyController { @@ -28,7 +26,6 @@ class ReportTaskController extends MyController { RxList selectedImages = [].obs; - // Controllers for each form field final assignedDateController = TextEditingController(); final workAreaController = TextEditingController(); final activityController = TextEditingController(); @@ -44,50 +41,37 @@ class ReportTaskController extends MyController { @override void onInit() { super.onInit(); - appLogger.i("Initializing ReportTaskController..."); - - basicValidator.addField('assigned_date', - label: "Assigned Date", controller: assignedDateController); - basicValidator.addField('work_area', - label: "Work Area", controller: workAreaController); - basicValidator.addField('activity', - label: "Activity", controller: activityController); - basicValidator.addField('team_size', - label: "Team Size", controller: teamSizeController); - basicValidator.addField('task_id', - label: "Task Id", controller: taskIdController); - basicValidator.addField('assigned', - label: "Assigned", controller: assignedController); - basicValidator.addField('completed_work', - label: "Completed Work", - required: true, - controller: completedWorkController); - basicValidator.addField('comment', - label: "Comment", required: true, controller: commentController); - basicValidator.addField('assigned_by', - label: "Assigned By", controller: assignedByController); - basicValidator.addField('team_members', - label: "Team Members", controller: teamMembersController); - basicValidator.addField('planned_work', - label: "Planned Work", controller: plannedWorkController); - - appLogger.i( - "Fields initialized for assigned_date, work_area, activity, team_size, assigned, completed_work, and comment."); + logSafe("Initializing ReportTaskController..."); + basicValidator + ..addField('assigned_date', label: "Assigned Date", controller: assignedDateController) + ..addField('work_area', label: "Work Area", controller: workAreaController) + ..addField('activity', label: "Activity", controller: activityController) + ..addField('team_size', label: "Team Size", controller: teamSizeController) + ..addField('task_id', label: "Task Id", controller: taskIdController) + ..addField('assigned', label: "Assigned", controller: assignedController) + ..addField('completed_work', label: "Completed Work", required: true, controller: completedWorkController) + ..addField('comment', label: "Comment", required: true, controller: commentController) + ..addField('assigned_by', label: "Assigned By", controller: assignedByController) + ..addField('team_members', label: "Team Members", controller: teamMembersController) + ..addField('planned_work', label: "Planned Work", controller: plannedWorkController); + logSafe("Form fields initialized."); } @override void onClose() { - assignedDateController.dispose(); - workAreaController.dispose(); - activityController.dispose(); - teamSizeController.dispose(); - taskIdController.dispose(); - assignedController.dispose(); - completedWorkController.dispose(); - commentController.dispose(); - assignedByController.dispose(); - teamMembersController.dispose(); - plannedWorkController.dispose(); + [ + assignedDateController, + workAreaController, + activityController, + teamSizeController, + taskIdController, + assignedController, + completedWorkController, + commentController, + assignedByController, + teamMembersController, + plannedWorkController, + ].forEach((controller) => controller.dispose()); super.onClose(); } @@ -99,36 +83,16 @@ class ReportTaskController extends MyController { required DateTime reportedDate, List? images, }) async { - appLogger.i("Starting task report..."); - + logSafe("Reporting task for projectId", sensitive: true); final completedWork = completedWorkController.text.trim(); - - if (completedWork.isEmpty) { - showAppSnackbar( - title: "Error", - message: "Completed work is required.", - type: SnackbarType.error, - ); - return false; - } - - final completedWorkInt = int.tryParse(completedWork); - if (completedWorkInt == null || completedWorkInt < 0) { - showAppSnackbar( - title: "Error", - message: "Completed work must be a positive integer.", - type: SnackbarType.error, - ); + if (completedWork.isEmpty || int.tryParse(completedWork) == null || int.parse(completedWork) < 0) { + _showError("Completed work must be a positive number."); return false; } final commentField = commentController.text.trim(); if (commentField.isEmpty) { - showAppSnackbar( - title: "Error", - message: "Comment is required.", - type: SnackbarType.error, - ); + _showError("Comment is required."); return false; } @@ -136,63 +100,30 @@ class ReportTaskController extends MyController { reportStatus.value = ApiStatus.loading; isLoading.value = true; - List>? imageData; - if (images != null && images.isNotEmpty) { - final imageFutures = images.map((file) async { - final compressedBytes = await compressImageToUnder100KB(file); - if (compressedBytes == null) return null; - - final base64Image = base64Encode(compressedBytes); - final fileName = file.path.split('/').last; - final contentType = _getContentTypeFromFileName(fileName); - - return { - "fileName": fileName, - "base64Data": base64Image, - "contentType": contentType, - "fileSize": compressedBytes.lengthInBytes, - "description": "Image uploaded for task report", - }; - }).toList(); - - final results = await Future.wait(imageFutures); - imageData = results.whereType>().toList(); - } + final imageData = await _prepareImages(images, "task report"); final success = await ApiService.reportTask( id: projectId, comment: commentField, - completedTask: completedWorkInt, + completedTask: int.parse(completedWork), checkList: checklist, images: imageData, ); if (success) { reportStatus.value = ApiStatus.success; - showAppSnackbar( - title: "Success", - message: "Task reported successfully!", - type: SnackbarType.success, - ); + _showSuccess("Task reported successfully!"); await taskController.fetchTaskData(projectId); return true; } else { reportStatus.value = ApiStatus.failure; - showAppSnackbar( - title: "Error", - message: "Failed to report task.", - type: SnackbarType.error, - ); + _showError("Failed to report task."); return false; } - } catch (e) { - appLogger.e("Error reporting task: $e"); + } catch (e, s) { + logSafe("Exception while reporting task", level: LogLevel.error, error: e, stackTrace: s); reportStatus.value = ApiStatus.failure; - showAppSnackbar( - title: "Error", - message: "An error occurred while reporting the task.", - type: SnackbarType.error, - ); + _showError("An error occurred while reporting the task."); return false; } finally { isLoading.value = false; @@ -202,121 +133,116 @@ class ReportTaskController extends MyController { } } - String _getContentTypeFromFileName(String fileName) { - final ext = fileName.split('.').last.toLowerCase(); - switch (ext) { - case 'jpg': - case 'jpeg': - return 'image/jpeg'; - case 'png': - return 'image/png'; - case 'webp': - return 'image/webp'; - case 'gif': - return 'image/gif'; - default: - return 'application/octet-stream'; - } - } - Future commentTask({ required String projectId, required String comment, List? images, }) async { - appLogger.i("Starting task comment..."); + logSafe("Submitting comment for project", sensitive: true); final commentField = commentController.text.trim(); if (commentField.isEmpty) { - showAppSnackbar( - title: "Error", - message: "Comment is required.", - type: SnackbarType.error, - ); + _showError("Comment is required."); return; } try { isLoading.value = true; - List>? imageData; - - if (images != null && images.isNotEmpty) { - final imageFutures = images.map((file) async { - final compressedBytes = await compressImageToUnder100KB(file); - if (compressedBytes == null) return null; - - final base64Image = base64Encode(compressedBytes); - final fileName = file.path.split('/').last; - final contentType = _getContentTypeFromFileName(fileName); - - return { - "fileName": fileName, - "base64Data": base64Image, - "contentType": contentType, - "fileSize": compressedBytes.lengthInBytes, - "description": "Image uploaded for task comment", - }; - }).toList(); - - final results = await Future.wait(imageFutures); - imageData = results.whereType>().toList(); - } + final imageData = await _prepareImages(images, "task comment"); final success = await ApiService.commentTask( id: projectId, comment: commentField, images: imageData, ).timeout(const Duration(seconds: 30), onTimeout: () { - appLogger.e("Request timed out."); + logSafe("Task comment request timed out.", level: LogLevel.error); throw Exception("Request timed out."); }); if (success) { - showAppSnackbar( - title: "Success", - message: "Task commented successfully!", - type: SnackbarType.success, - ); + _showSuccess("Task commented successfully!"); await taskController.fetchTaskData(projectId); } else { - showAppSnackbar( - title: "Error", - message: "Failed to comment task.", - type: SnackbarType.error, - ); + _showError("Failed to comment task."); } - } catch (e) { - appLogger.e("Error commenting task: $e"); - showAppSnackbar( - title: "Error", - message: "An error occurred while commenting the task.", - type: SnackbarType.error, - ); + } catch (e, s) { + logSafe("Exception while commenting task", level: LogLevel.error, error: e, stackTrace: s); + _showError("An error occurred while commenting the task."); } finally { isLoading.value = false; } } - Future pickImages({required bool fromCamera}) async { - if (fromCamera) { - final pickedFile = await _picker.pickImage( - source: ImageSource.camera, - imageQuality: 75, - ); - if (pickedFile != null) { - selectedImages.add(File(pickedFile.path)); + Future>?> _prepareImages(List? images, String context) async { + if (images == null || images.isEmpty) return null; + + logSafe("Preparing images for $context upload..."); + + final results = await Future.wait(images.map((file) async { + try { + final compressed = await compressImageToUnder100KB(file); + if (compressed == null) return null; + + return { + "fileName": file.path.split('/').last, + "base64Data": base64Encode(compressed), + "contentType": _getContentTypeFromFileName(file.path), + "fileSize": compressed.lengthInBytes, + "description": "Image uploaded for $context", + }; + } catch (e) { + logSafe("Image processing failed: ${file.path}", level: LogLevel.warning, error: e); + return null; } - } else { - final pickedFiles = await _picker.pickMultiImage(imageQuality: 75); - if (pickedFiles.isNotEmpty) { + })); + + return results.whereType>().toList(); + } + + String _getContentTypeFromFileName(String fileName) { + final ext = fileName.split('.').last.toLowerCase(); + return switch (ext) { + 'jpg' || 'jpeg' => 'image/jpeg', + 'png' => 'image/png', + 'webp' => 'image/webp', + 'gif' => 'image/gif', + _ => 'application/octet-stream', + }; + } + + Future pickImages({required bool fromCamera}) async { + try { + if (fromCamera) { + final pickedFile = await _picker.pickImage(source: ImageSource.camera, imageQuality: 75); + if (pickedFile != null) { + selectedImages.add(File(pickedFile.path)); + } + } else { + final pickedFiles = await _picker.pickMultiImage(imageQuality: 75); selectedImages.addAll(pickedFiles.map((xfile) => File(xfile.path))); } + logSafe("Images picked: ${selectedImages.length}", sensitive: true); + } catch (e) { + logSafe("Error picking images", level: LogLevel.warning, error: e); } } void removeImageAt(int index) { if (index >= 0 && index < selectedImages.length) { selectedImages.removeAt(index); + logSafe("Removed image at index $index"); } } + + void _showError(String message) => showAppSnackbar( + title: "Error", + message: message, + type: SnackbarType.error, + ); + + void _showSuccess(String message) => showAppSnackbar( + title: "Success", + message: message, + type: SnackbarType.success, + ); } diff --git a/lib/helpers/services/api_service.dart b/lib/helpers/services/api_service.dart index badcd68..805446b 100644 --- a/lib/helpers/services/api_service.dart +++ b/lib/helpers/services/api_service.dart @@ -9,63 +9,60 @@ import 'package:marco/helpers/services/api_endpoints.dart'; import 'package:marco/helpers/services/storage/local_storage.dart'; import 'package:jwt_decoder/jwt_decoder.dart'; -import 'package:marco/helpers/services/app_logger.dart'; +import 'package:marco/helpers/services/app_logger.dart'; + class ApiService { static const Duration timeout = Duration(seconds: 30); static const bool enableLogs = true; static const Duration extendedTimeout = Duration(seconds: 60); - // === Helpers === + static Future _getToken() async { + final token = await LocalStorage.getJwtToken(); - static Future _getToken() async { - final token = await LocalStorage.getJwtToken(); - - if (token == null) { - if (enableLogs) appLogger.w("No JWT token found."); - return null; - } - - try { - // Check if the token is expired - if (JwtDecoder.isExpired(token)) { - _log("Access token is expired. Attempting refresh..."); - final refreshed = await AuthService.refreshToken(); - if (refreshed) { - return await LocalStorage.getJwtToken(); - } else { - _log("Token refresh failed. Logging out..."); - await LocalStorage.logout(); - return null; - } + if (token == null) { + logSafe("No JWT token found."); + return null; } - // Check if token is about to expire in < 2 minutes - final expirationDate = JwtDecoder.getExpirationDate(token); - final now = DateTime.now(); - final difference = expirationDate.difference(now); - - if (difference.inMinutes < 2) { - _log("Access token is about to expire in ${difference.inSeconds}s. Refreshing..."); - final refreshed = await AuthService.refreshToken(); - if (refreshed) { - return await LocalStorage.getJwtToken(); + try { + if (JwtDecoder.isExpired(token)) { + logSafe("Access token is expired. Attempting refresh..."); + final refreshed = await AuthService.refreshToken(); + if (refreshed) { + return await LocalStorage.getJwtToken(); + } else { + logSafe("Token refresh failed. Logging out..."); + await LocalStorage.logout(); + return null; + } } + + final expirationDate = JwtDecoder.getExpirationDate(token); + final now = DateTime.now(); + final difference = expirationDate.difference(now); + + if (difference.inMinutes < 2) { + logSafe( + "Access token is about to expire in ${difference.inSeconds}s. Refreshing..."); + final refreshed = await AuthService.refreshToken(); + if (refreshed) { + return await LocalStorage.getJwtToken(); + } + } + } catch (e) { + logSafe("Token decoding error: $e", level: LogLevel.error); } - } catch (e) { - _log("Token decoding error: $e"); + return token; } - return token; -} - static Map _headers(String token) => { 'Content-Type': 'application/json', 'Authorization': 'Bearer $token', }; static void _log(String message) { - if (enableLogs) appLogger.i(message); + if (enableLogs) logSafe(message); } static dynamic _parseResponse(http.Response response, {String label = ''}) { @@ -82,29 +79,27 @@ class ApiService { return null; } -static dynamic _parseResponseForAllData(http.Response response, - {String label = ''}) { - _log("$label Response: ${response.body}"); + static dynamic _parseResponseForAllData(http.Response response, + {String label = ''}) { + _log("$label Response: ${response.body}"); - try { - final body = response.body.trim(); - if (body.isEmpty) throw FormatException("Empty response body"); + try { + final body = response.body.trim(); + if (body.isEmpty) throw FormatException("Empty response body"); - final json = jsonDecode(body); + final json = jsonDecode(body); + if (response.statusCode == 200 && json['success'] == true) { + return json; + } - if (response.statusCode == 200 && json['success'] == true) { - return json; + _log("API Error [$label]: ${json['message'] ?? 'Unknown error'}"); + } catch (e) { + _log("Response parsing error [$label]: $e"); } - _log("API Error [$label]: ${json['message'] ?? 'Unknown error'}"); - } catch (e) { - _log("Response parsing error [$label]: $e"); + return null; } - return null; -} - - static Future _getRequest( String endpoint, { Map? queryParams, @@ -115,22 +110,22 @@ static dynamic _parseResponseForAllData(http.Response response, final uri = Uri.parse("${ApiEndpoints.baseUrl}$endpoint") .replace(queryParameters: queryParams); - _log("GET $uri"); + logSafe("GET $uri"); try { final response = await http.get(uri, headers: _headers(token)).timeout(timeout); if (response.statusCode == 401 && !hasRetried) { - _log("Unauthorized. Attempting token refresh..."); + logSafe("Unauthorized. Attempting token refresh..."); if (await AuthService.refreshToken()) { return await _getRequest(endpoint, queryParams: queryParams, hasRetried: true); } - _log("Token refresh failed."); + logSafe("Token refresh failed."); } return response; } catch (e) { - _log("HTTP GET Exception: $e"); + logSafe("HTTP GET Exception: $e", level: LogLevel.error); return null; } } @@ -145,7 +140,8 @@ static dynamic _parseResponseForAllData(http.Response response, if (token == null) return null; final uri = Uri.parse("${ApiEndpoints.baseUrl}$endpoint"); - _log("POST $uri\nHeaders: ${_headers(token)}\nBody: $body"); + logSafe("POST $uri\nHeaders: ${_headers(token)}\nBody: $body", + sensitive: true); try { final response = await http @@ -153,7 +149,7 @@ static dynamic _parseResponseForAllData(http.Response response, .timeout(customTimeout); if (response.statusCode == 401 && !hasRetried) { - _log("Unauthorized POST. Attempting token refresh..."); + logSafe("Unauthorized POST. Attempting token refresh..."); if (await AuthService.refreshToken()) { return await _postRequest(endpoint, body, customTimeout: customTimeout, hasRetried: true); @@ -161,12 +157,12 @@ static dynamic _parseResponseForAllData(http.Response response, } return response; } catch (e) { - _log("HTTP POST Exception: $e"); + logSafe("HTTP POST Exception: $e", level: LogLevel.error); return null; } } -// === Dashboard Endpoints === + // === Dashboard Endpoints === static Future?> getDashboardAttendanceOverview( String projectId, int days) async { @@ -263,7 +259,7 @@ static dynamic _parseResponseForAllData(http.Response response, "base64Data": base64Encode(bytes), }; } catch (e) { - _log("Image encoding error: $e"); + logSafe("Image encoding error: $e", level: LogLevel.error); return false; } } @@ -278,7 +274,7 @@ static dynamic _parseResponseForAllData(http.Response response, final json = jsonDecode(response.body); if (response.statusCode == 200 && json['success'] == true) return true; - _log("Failed to upload image: ${json['message'] ?? 'Unknown error'}"); + logSafe("Failed to upload image: ${json['message'] ?? 'Unknown error'}"); return false; } @@ -389,7 +385,7 @@ static dynamic _parseResponseForAllData(http.Response response, Get.back(); return true; } - _log("Failed to report task: ${json['message'] ?? 'Unknown error'}"); + logSafe("Failed to report task: ${json['message'] ?? 'Unknown error'}"); return false; } @@ -443,19 +439,19 @@ static dynamic _parseResponseForAllData(http.Response response, Get.back(); return true; } - _log("Failed to assign daily task: ${json['message'] ?? 'Unknown error'}"); + logSafe( + "Failed to assign daily task: ${json['message'] ?? 'Unknown error'}"); return false; } static Future?> getWorkStatus() async { final res = await _getRequest(ApiEndpoints.getWorkStatus); if (res == null) { - _log('Work Status API returned null'); + logSafe('Work Status API returned null'); return null; } - _log('Work Status raw response: ${res.body}'); - + logSafe('Work Status raw response: ${res.body}'); return _parseResponseForAllData(res, label: 'Work Status') as Map?; } @@ -465,6 +461,7 @@ static dynamic _parseResponseForAllData(http.Response response, res != null ? _parseResponseForAllData(res, label: 'Master Work Categories') : null); + static Future approveTask({ required String id, required String comment, @@ -517,7 +514,7 @@ static dynamic _parseResponseForAllData(http.Response response, return true; } - _log("Failed to create task: ${json['message'] ?? 'Unknown error'}"); + logSafe("Failed to create task: ${json['message'] ?? 'Unknown error'}"); return false; } } diff --git a/lib/helpers/services/app_initializer.dart b/lib/helpers/services/app_initializer.dart index 037b8d5..258e3be 100644 --- a/lib/helpers/services/app_initializer.dart +++ b/lib/helpers/services/app_initializer.dart @@ -6,23 +6,43 @@ import 'package:marco/helpers/services/storage/local_storage.dart'; import 'package:marco/helpers/theme/theme_customizer.dart'; import 'package:marco/helpers/theme/app_theme.dart'; import 'package:url_strategy/url_strategy.dart'; -import 'package:marco/helpers/services/app_logger.dart'; - - +import 'package:marco/helpers/services/app_logger.dart'; Future initializeApp() async { - setPathUrlStrategy(); + try { + logSafe("Starting app initialization..."); - SystemChrome.setSystemUIOverlayStyle(const SystemUiOverlayStyle( - statusBarColor: Color.fromARGB(255, 255, 0, 0), - statusBarIconBrightness: Brightness.light, - )); + setPathUrlStrategy(); + logSafe("URL strategy set."); - await LocalStorage.init(); - await ThemeCustomizer.init(); - Get.put(PermissionController()); - Get.put(ProjectController(), permanent: true); - AppStyle.init(); + SystemChrome.setSystemUIOverlayStyle(const SystemUiOverlayStyle( + statusBarColor: Color.fromARGB(255, 255, 0, 0), + statusBarIconBrightness: Brightness.light, + )); + logSafe("System UI overlay style set."); - appLogger.i("App initialization completed successfully."); + await LocalStorage.init(); + logSafe("Local storage initialized."); + + await ThemeCustomizer.init(); + logSafe("Theme customizer initialized."); + + Get.put(PermissionController()); + logSafe("PermissionController injected."); + + Get.put(ProjectController(), permanent: true); + logSafe("ProjectController injected as permanent."); + + AppStyle.init(); + logSafe("AppStyle initialized."); + + logSafe("App initialization completed successfully."); + } catch (e, stacktrace) { + logSafe("Error during app initialization", + level: LogLevel.error, + error: e, + stackTrace: stacktrace, + ); + rethrow; + } } diff --git a/lib/helpers/services/app_logger.dart b/lib/helpers/services/app_logger.dart index 458f39d..7afdc4d 100644 --- a/lib/helpers/services/app_logger.dart +++ b/lib/helpers/services/app_logger.dart @@ -12,10 +12,21 @@ late final FileLogOutput fileLogOutput; /// Initialize logging (call once in `main()`) Future initLogging() async { await requestStoragePermission(); + fileLogOutput = FileLogOutput(); + appLogger = Logger( - printer: SimpleFileLogPrinter(), - output: fileLogOutput, + printer: PrettyPrinter( + methodCount: 0, + printTime: true, + colors: true, + printEmojis: true, + ), + output: MultiOutput([ + ConsoleOutput(), // ✅ Console will use the top-level PrettyPrinter + fileLogOutput, // ✅ File will still use the SimpleFileLogPrinter + ]), + level: Level.debug, ); } @@ -27,6 +38,34 @@ Future requestStoragePermission() async { } } +/// Safe logger wrapper +void logSafe( + String message, { + LogLevel level = LogLevel.info, + dynamic error, + StackTrace? stackTrace, + bool sensitive = false, +}) { + if (sensitive) return; + + switch (level) { + case LogLevel.debug: + appLogger.d(message, error: error, stackTrace: stackTrace); + break; + case LogLevel.warning: + appLogger.w(message, error: error, stackTrace: stackTrace); + break; + case LogLevel.error: + appLogger.e(message, error: error, stackTrace: stackTrace); + break; + case LogLevel.verbose: + appLogger.v(message, error: error, stackTrace: stackTrace); + break; + default: + appLogger.i(message, error: error, stackTrace: stackTrace); + } +} + /// Custom log output that writes to a local `.txt` file class FileLogOutput extends LogOutput { File? _logFile; @@ -54,6 +93,9 @@ class FileLogOutput extends LogOutput { @override void output(OutputEvent event) async { await _init(); + + if (event.lines.isEmpty) return; + final logMessage = event.lines.join('\n') + '\n'; await _logFile!.writeAsString( logMessage, @@ -97,11 +139,18 @@ class FileLogOutput extends LogOutput { class SimpleFileLogPrinter extends LogPrinter { @override List log(LogEvent event) { + final message = event.message.toString(); + + if (message.contains('[SENSITIVE]')) return []; + final timestamp = DateFormat('yyyy-MM-dd HH:mm:ss').format(DateTime.now()); final level = event.level.name.toUpperCase(); - final message = event.message; final error = event.error != null ? ' | ERROR: ${event.error}' : ''; - final stack = event.stackTrace != null ? '\nSTACKTRACE:\n${event.stackTrace}' : ''; + final stack = + event.stackTrace != null ? '\nSTACKTRACE:\n${event.stackTrace}' : ''; return ['[$timestamp] [$level] $message$error$stack']; } } + +/// Optional log level enum for better type safety +enum LogLevel { debug, info, warning, error, verbose } diff --git a/lib/helpers/services/auth_service.dart b/lib/helpers/services/auth_service.dart index f9e2c4b..bff8272 100644 --- a/lib/helpers/services/auth_service.dart +++ b/lib/helpers/services/auth_service.dart @@ -6,7 +6,7 @@ import 'package:marco/controller/permission_controller.dart'; import 'package:marco/controller/project_controller.dart'; import 'package:marco/helpers/services/api_endpoints.dart'; import 'package:marco/helpers/services/storage/local_storage.dart'; -import 'package:marco/helpers/services/app_logger.dart'; +import 'package:marco/helpers/services/app_logger.dart'; class AuthService { static const String _baseUrl = ApiEndpoints.baseUrl; @@ -19,6 +19,7 @@ class AuthService { /// Login with email and password static Future?> loginUser(Map data) async { try { + logSafe("Attempting login..."); final response = await http.post( Uri.parse("$_baseUrl/auth/login-mobile"), headers: _headers, @@ -30,12 +31,14 @@ class AuthService { await _handleLoginSuccess(responseData['data']); return null; } else if (response.statusCode == 401) { + logSafe("Invalid login credentials.", level: LogLevel.warning); return {"password": "Invalid email or password"}; } else { + logSafe("Login error: ${responseData['message']}", level: LogLevel.warning); return {"error": responseData['message'] ?? "Unexpected error occurred"}; } - } catch (e) { - appLogger.e("Login error: $e"); + } catch (e, stacktrace) { + logSafe("Login exception", level: LogLevel.error, error: e, stackTrace: stacktrace); return {"error": "Network error. Please check your connection."}; } } @@ -46,7 +49,7 @@ class AuthService { final refreshToken = await LocalStorage.getRefreshToken(); if (accessToken == null || refreshToken == null || accessToken.isEmpty || refreshToken.isEmpty) { - appLogger.w("Missing access/refresh token."); + logSafe("Missing access or refresh token.", level: LogLevel.warning); return false; } @@ -56,6 +59,7 @@ class AuthService { }; try { + logSafe("Refreshing token..."); final response = await http.post( Uri.parse("$_baseUrl/auth/refresh-token"), headers: _headers, @@ -67,14 +71,14 @@ class AuthService { await LocalStorage.setJwtToken(data['data']['token']); await LocalStorage.setRefreshToken(data['data']['refreshToken']); await LocalStorage.setLoggedInUser(true); - appLogger.i("Token refreshed."); + logSafe("Token refreshed successfully."); return true; } else { - appLogger.w("Refresh token failed: ${data['message']}"); + logSafe("Refresh token failed: ${data['message']}", level: LogLevel.warning); return false; } - } catch (e) { - appLogger.e("Token refresh error: $e"); + } catch (e, stacktrace) { + logSafe("Token refresh exception", level: LogLevel.error, error: e, stackTrace: stacktrace); return false; } } @@ -82,6 +86,7 @@ class AuthService { /// Forgot password static Future?> forgotPassword(String email) async { try { + logSafe("Forgot password requested."); final response = await http.post( Uri.parse("$_baseUrl/auth/forgot-password"), headers: _headers, @@ -91,8 +96,8 @@ class AuthService { final data = jsonDecode(response.body); if (response.statusCode == 200 && data['success'] == true) return null; return {"error": data['message'] ?? "Failed to send reset link."}; - } catch (e) { - appLogger.e("Forgot password error: $e"); + } catch (e, stacktrace) { + logSafe("Forgot password error", level: LogLevel.error, error: e, stackTrace: stacktrace); return {"error": "Network error. Please check your connection."}; } } @@ -100,6 +105,7 @@ class AuthService { /// Request demo static Future?> requestDemo(Map demoData) async { try { + logSafe("Submitting demo request..."); final response = await http.post( Uri.parse("$_baseUrl/market/inquiry"), headers: _headers, @@ -109,8 +115,8 @@ class AuthService { final data = jsonDecode(response.body); if (response.statusCode == 200 && data['success'] == true) return null; return {"error": data['message'] ?? "Failed to submit demo request."}; - } catch (e) { - appLogger.e("Request demo error: $e"); + } catch (e, stacktrace) { + logSafe("Request demo error", level: LogLevel.error, error: e, stackTrace: stacktrace); return {"error": "Network error. Please check your connection."}; } } @@ -118,6 +124,7 @@ class AuthService { /// Get list of industries static Future>?> getIndustries() async { try { + logSafe("Fetching industries list..."); final response = await http.get( Uri.parse("$_baseUrl/market/industries"), headers: _headers, @@ -128,8 +135,8 @@ class AuthService { return List>.from(data['data']); } return null; - } catch (e) { - appLogger.e("Get industries error: $e"); + } catch (e, stacktrace) { + logSafe("Get industries error", level: LogLevel.error, error: e, stackTrace: stacktrace); return null; } } @@ -142,6 +149,7 @@ class AuthService { final token = await LocalStorage.getJwtToken(); try { + logSafe("Generating MPIN..."); final response = await http.post( Uri.parse("$_baseUrl/auth/generate-mpin"), headers: { @@ -154,8 +162,8 @@ class AuthService { final data = jsonDecode(response.body); if (response.statusCode == 200 && data['success'] == true) return null; return {"error": data['message'] ?? "Failed to generate MPIN."}; - } catch (e) { - appLogger.e("Generate MPIN error: $e"); + } catch (e, stacktrace) { + logSafe("Generate MPIN error", level: LogLevel.error, error: e, stackTrace: stacktrace); return {"error": "Network error. Please check your connection."}; } } @@ -171,6 +179,7 @@ class AuthService { final token = await LocalStorage.getJwtToken(); try { + logSafe("Verifying MPIN..."); final response = await http.post( Uri.parse("$_baseUrl/auth/login-mpin"), headers: { @@ -187,8 +196,8 @@ class AuthService { final data = jsonDecode(response.body); if (response.statusCode == 200 && data['success'] == true) return null; return {"error": data['message'] ?? "MPIN verification failed."}; - } catch (e) { - appLogger.e("Verify MPIN error: $e"); + } catch (e, stacktrace) { + logSafe("Verify MPIN error", level: LogLevel.error, error: e, stackTrace: stacktrace); return {"error": "Network error. Please check your connection."}; } } @@ -196,6 +205,7 @@ class AuthService { /// Generate OTP static Future?> generateOtp(String email) async { try { + logSafe("Generating OTP for email..."); final response = await http.post( Uri.parse("$_baseUrl/auth/send-otp"), headers: _headers, @@ -205,8 +215,8 @@ class AuthService { final data = jsonDecode(response.body); if (response.statusCode == 200 && data['success'] == true) return null; return {"error": data['message'] ?? "Failed to generate OTP."}; - } catch (e) { - appLogger.e("Generate OTP error: $e"); + } catch (e, stacktrace) { + logSafe("Generate OTP error", level: LogLevel.error, error: e, stackTrace: stacktrace); return {"error": "Network error. Please check your connection."}; } } @@ -217,6 +227,7 @@ class AuthService { required String otp, }) async { try { + logSafe("Verifying OTP..."); final response = await http.post( Uri.parse("$_baseUrl/auth/login-otp"), headers: _headers, @@ -229,14 +240,16 @@ class AuthService { return null; } return {"error": data['message'] ?? "OTP verification failed."}; - } catch (e) { - appLogger.e("Verify OTP error: $e"); + } catch (e, stacktrace) { + logSafe("Verify OTP error", level: LogLevel.error, error: e, stackTrace: stacktrace); return {"error": "Network error. Please check your connection."}; } } /// Handle login success flow static Future _handleLoginSuccess(Map data) async { + logSafe("Processing login success..."); + final jwtToken = data['token']; final refreshToken = data['refreshToken']; final mpinToken = data['mpinToken']; @@ -256,9 +269,10 @@ class AuthService { final permissionController = Get.put(PermissionController()); await permissionController.loadData(jwtToken); + await Get.find().fetchProjects(); isLoggedIn = true; - appLogger.i("Login success initialized."); + logSafe("Login flow completed."); } } diff --git a/lib/helpers/services/permission_service.dart b/lib/helpers/services/permission_service.dart index 95fd34e..aa9d492 100644 --- a/lib/helpers/services/permission_service.dart +++ b/lib/helpers/services/permission_service.dart @@ -2,7 +2,7 @@ import 'dart:convert'; import 'package:get/get.dart'; import 'package:http/http.dart' as http; -import 'package:marco/helpers/services/app_logger.dart'; +import 'package:marco/helpers/services/app_logger.dart'; import 'package:marco/model/user_permission.dart'; import 'package:marco/model/employee_info.dart'; import 'package:marco/model/projects_model.dart'; @@ -10,18 +10,19 @@ import 'package:marco/helpers/services/storage/local_storage.dart'; import 'package:marco/helpers/services/auth_service.dart'; import 'package:marco/helpers/services/api_endpoints.dart'; - - class PermissionService { static final Map> _userDataCache = {}; -static const String _baseUrl = ApiEndpoints.baseUrl; + static const String _baseUrl = ApiEndpoints.baseUrl; + /// Fetches all user-related data (permissions, employee info, projects) static Future> fetchAllUserData( String token, { bool hasRetried = false, }) async { - // Return cached data if already available + logSafe("Fetching user data...", sensitive: true); + if (_userDataCache.containsKey(token)) { + logSafe("User data cache hit.", sensitive: true); return _userDataCache[token]!; } @@ -30,8 +31,10 @@ static const String _baseUrl = ApiEndpoints.baseUrl; try { final response = await http.get(uri, headers: headers); + final statusCode = response.statusCode; - if (response.statusCode == 200) { + if (statusCode == 200) { + logSafe("User data fetched successfully."); final data = json.decode(response.body)['data']; final result = { @@ -44,8 +47,9 @@ static const String _baseUrl = ApiEndpoints.baseUrl; return result; } - // Handle 401 by attempting a single retry with refreshed token - if (response.statusCode == 401 && !hasRetried) { + if (statusCode == 401 && !hasRetried) { + logSafe("Unauthorized. Attempting token refresh...", level: LogLevel.warning); + final refreshed = await AuthService.refreshToken(); if (refreshed) { final newToken = await LocalStorage.getJwtToken(); @@ -55,19 +59,23 @@ static const String _baseUrl = ApiEndpoints.baseUrl; } await _handleUnauthorized(); + logSafe("Token refresh failed. Redirecting to login.", level: LogLevel.warning); throw Exception('Unauthorized. Token refresh failed.'); } final error = json.decode(response.body)['message'] ?? 'Unknown error'; + logSafe("Failed to fetch user data: $error", level: LogLevel.warning); throw Exception('Failed to fetch user data: $error'); - } catch (e) { - appLogger.e('Error fetching user data: $e'); + } catch (e, stacktrace) { + logSafe("Exception while fetching user data", level: LogLevel.error, error: e, stackTrace: stacktrace); rethrow; } } /// Clears auth data and redirects to login static Future _handleUnauthorized() async { + logSafe("Clearing tokens and redirecting to login due to unauthorized access.", level: LogLevel.warning); + await LocalStorage.removeToken('jwt_token'); await LocalStorage.removeToken('refresh_token'); await LocalStorage.setLoggedInUser(false); @@ -76,6 +84,7 @@ static const String _baseUrl = ApiEndpoints.baseUrl; /// Converts raw permission data into list of `UserPermission` static List _parsePermissions(List permissions) { + logSafe("Parsing user permissions..."); return permissions .map((id) => UserPermission.fromJson({'id': id})) .toList(); @@ -83,11 +92,13 @@ static const String _baseUrl = ApiEndpoints.baseUrl; /// Converts raw employee JSON into `EmployeeInfo` static EmployeeInfo _parseEmployeeInfo(Map data) { + logSafe("Parsing employee info..."); return EmployeeInfo.fromJson(data); } /// Converts raw projects JSON into list of `ProjectInfo` static List _parseProjectsInfo(List projects) { + logSafe("Parsing projects info..."); return projects.map((proj) => ProjectInfo.fromJson(proj)).toList(); } } diff --git a/lib/helpers/widgets/my_image_compressor.dart b/lib/helpers/widgets/my_image_compressor.dart index fdbd3b1..a885499 100644 --- a/lib/helpers/widgets/my_image_compressor.dart +++ b/lib/helpers/widgets/my_image_compressor.dart @@ -8,40 +8,61 @@ import 'package:marco/helpers/services/app_logger.dart'; Future compressImageToUnder100KB(File file) async { - int quality = 40; + int quality = 40; Uint8List? result; const int maxWidth = 800; const int maxHeight = 800; + logSafe("Starting image compression...", sensitive: true); + while (quality >= 10) { - result = await FlutterImageCompress.compressWithFile( - file.absolute.path, - quality: quality, - minWidth: maxWidth, - minHeight: maxHeight, - format: CompressFormat.jpeg, - ); + try { + result = await FlutterImageCompress.compressWithFile( + file.absolute.path, + quality: quality, + minWidth: maxWidth, + minHeight: maxHeight, + format: CompressFormat.jpeg, + ); - if (result != null) { - appLogger.i('Quality: $quality, Size: ${(result.lengthInBytes / 1024).toStringAsFixed(2)} KB'); + if (result != null) { + logSafe( + 'Compression quality: $quality, size: ${(result.lengthInBytes / 1024).toStringAsFixed(2)} KB', + ); - if (result.lengthInBytes <= 100 * 1024) { - return result; + if (result.lengthInBytes <= 100 * 1024) { + logSafe("Image compressed successfully under 100KB."); + return result; + } + } else { + logSafe("Compression returned null at quality $quality", level: LogLevel.warning); } + } catch (e, stacktrace) { + logSafe("Compression error at quality $quality", level: LogLevel.error, error: e, stackTrace: stacktrace); } - quality -= 10; + quality -= 10; } + logSafe("Failed to compress image under 100KB. Returning best effort result.", level: LogLevel.warning); return result; } + Future saveCompressedImageToFile(Uint8List bytes) async { - final tempDir = await getTemporaryDirectory(); - final filePath = path.join( - tempDir.path, - 'compressed_${DateTime.now().millisecondsSinceEpoch}.jpg', - ); - final file = File(filePath); - return await file.writeAsBytes(bytes); + try { + final tempDir = await getTemporaryDirectory(); + final filePath = path.join( + tempDir.path, + 'compressed_${DateTime.now().millisecondsSinceEpoch}.jpg', + ); + final file = File(filePath); + final savedFile = await file.writeAsBytes(bytes); + + logSafe("Compressed image saved to ${savedFile.path}", sensitive: true); + return savedFile; + } catch (e, stacktrace) { + logSafe("Error saving compressed image", level: LogLevel.error, error: e, stackTrace: stacktrace); + rethrow; + } } diff --git a/lib/main.dart b/lib/main.dart index 74e3547..6529d15 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -8,12 +8,13 @@ import 'package:marco/helpers/services/app_logger.dart'; Future main() async { WidgetsFlutterBinding.ensureInitialized(); - await initLogging(); - - appLogger.i("App starting..."); + await initLogging(); + logSafe("App starting..."); try { await initializeApp(); + logSafe("App initialized successfully."); + runApp( ChangeNotifierProvider( create: (_) => AppNotifier(), @@ -21,12 +22,21 @@ Future main() async { ), ); } catch (e, stacktrace) { - appLogger.e('App failed to initialize:', error: e, stackTrace: stacktrace); + logSafe('App failed to initialize.', + level: LogLevel.error, + error: e, + stackTrace: stacktrace, + ); runApp( const MaterialApp( home: Scaffold( - body: Center(child: Text("Failed to initialize the app.")), + body: Center( + child: Text( + "Failed to initialize the app.", + style: TextStyle(color: Colors.red), + ), + ), ), ), ); diff --git a/lib/view/my_app.dart b/lib/view/my_app.dart index 96bc49a..605a78c 100644 --- a/lib/view/my_app.dart +++ b/lib/view/my_app.dart @@ -3,7 +3,7 @@ import 'package:get/get.dart'; import 'package:flutter_localizations/flutter_localizations.dart'; import 'package:provider/provider.dart'; -import 'package:marco/helpers/services/app_logger.dart'; +import 'package:marco/helpers/services/app_logger.dart'; import 'package:marco/helpers/extensions/app_localization_delegate.dart'; import 'package:marco/helpers/services/auth_service.dart'; import 'package:marco/helpers/services/localizations/language.dart'; @@ -14,33 +14,30 @@ import 'package:marco/helpers/theme/theme_customizer.dart'; import 'package:marco/helpers/theme/app_notifier.dart'; import 'package:marco/routes.dart'; - - class MyApp extends StatelessWidget { const MyApp({super.key}); Future _getInitialRoute() async { try { if (!AuthService.isLoggedIn) { - appLogger.i("User not logged in. Routing to /auth/login-option"); + logSafe("User not logged in. Routing to /auth/login-option"); return "/auth/login-option"; } final bool hasMpin = LocalStorage.getIsMpin(); - appLogger.i("MPIN enabled: $hasMpin"); + logSafe("MPIN enabled: $hasMpin", sensitive: true); if (hasMpin) { await LocalStorage.setBool("mpin_verified", false); - appLogger - .i("Routing to /auth/mpin-auth and setting mpin_verified to false"); + logSafe("Routing to /auth/mpin-auth and setting mpin_verified to false"); return "/auth/mpin-auth"; } else { - appLogger.i("MPIN not enabled. Routing to /home"); + logSafe("MPIN not enabled. Routing to /dashboard"); return "/dashboard"; } } catch (e, stacktrace) { - appLogger.e("Error determining initial route", - error: e, stackTrace: stacktrace); + logSafe("Error determining initial route", + level: LogLevel.error, error: e, stackTrace: stacktrace); return "/auth/login-option"; } } @@ -53,6 +50,8 @@ class MyApp extends StatelessWidget { future: _getInitialRoute(), builder: (context, snapshot) { if (snapshot.hasError) { + logSafe("FutureBuilder snapshot error", + level: LogLevel.error, error: snapshot.error); return const MaterialApp( home: Center(child: Text("Error determining route")), );