Refactor logging implementation across controllers and services

- Replaced instances of the Logger package with a custom appLogger for consistent logging.
- Introduced app_logger.dart to manage logging with file output and storage permissions.
- Updated all controllers (e.g., DashboardController, EmployeesScreenController, etc.) to use appLogger for logging messages.
- Ensured that logging messages are appropriately categorized (info, warning, error) throughout the application.
- Implemented a file logging mechanism to store logs in a designated directory.
- Cleaned up old log files to maintain only the most recent logs.
This commit is contained in:
Vaibhav Surve 2025-06-24 13:11:22 +05:30
parent ef60677c98
commit e6d05e247e
29 changed files with 525 additions and 304 deletions

View File

@ -3,6 +3,9 @@
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.READ_CONTACTS"/>
<uses-permission android:name="android.permission.WRITE_CONTACTS"/>
<uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />

View File

@ -5,12 +5,16 @@ 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/widgets/my_snackbar.dart';
import 'package:marco/helpers/services/app_logger.dart';
import 'package:marco/helpers/services/storage/local_storage.dart';
class ForgotPasswordController extends MyController {
MyFormValidator basicValidator = MyFormValidator();
bool showPassword = false;
final MyFormValidator basicValidator = MyFormValidator();
final RxBool isLoading = false.obs;
@override
void onInit() {
super.onInit();
basicValidator.addField(
'email',
required: true,
@ -18,49 +22,51 @@ class ForgotPasswordController extends MyController {
validators: [MyEmailValidator()],
controller: TextEditingController(text: "demo@example.com"),
);
super.onInit();
}
Future<void> onLogin() async {
if (basicValidator.validateForm()) {
update();
var errors = await AuthService.loginUser(basicValidator.getData());
if (errors != null) {
basicValidator.validateForm();
basicValidator.clearErrors();
}
Get.toNamed('/auth/reset_password');
update();
Future<void> onForgotPassword() async {
if (!basicValidator.validateForm()) return;
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);
if (result == null) {
// Success case
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,
type: SnackbarType.error,
);
appLogger.w("Failed to send reset password email for $email: $errorMessage");
}
}
/// New: Forgot password function
Future<void> onForgotPassword() async {
if (basicValidator.validateForm()) {
update();
final data = basicValidator.getData();
final email = data['email']?.toString() ?? '';
final result = await AuthService.forgotPassword(email);
if (result != null) {
showAppSnackbar(
title: "Success",
message: "Your password reset link was sent.",
type: SnackbarType.success,
);
} else {
showAppSnackbar(
title: "Success",
message: "Your password reset link was sent.",
type: SnackbarType.success,
);
}
update();
}
}
void gotoLogIn() {
Get.toNamed('/auth/login-option');
} 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');
}
}

View File

@ -6,6 +6,8 @@ import 'package:marco/helpers/widgets/my_form_validator.dart';
import 'package:marco/helpers/widgets/my_validators.dart';
import 'package:marco/helpers/services/storage/local_storage.dart';
import 'package:marco/helpers/widgets/my_snackbar.dart';
import 'package:marco/helpers/services/app_logger.dart'; // <-- logging
class LoginController extends MyController {
final MyFormValidator basicValidator = MyFormValidator();
@ -51,40 +53,38 @@ class LoginController extends MyController {
isLoading.value = true;
final errors = await AuthService.loginUser(basicValidator.getData());
try {
final loginData = basicValidator.getData();
appLogger.i("Attempting login for user: ${loginData['username']}");
if (errors != null) {
showAppSnackbar(
title: "Login Failed",
message: "Username or password is incorrect",
type: SnackbarType.error,
);
final errors = await AuthService.loginUser(loginData);
basicValidator.addErrors(errors);
basicValidator.validateForm();
basicValidator.clearErrors();
} else {
await _handleRememberMe();
final bool isMpinEnabled = LocalStorage.getIsMpin();
print('MPIN Enabled? $isMpinEnabled');
if (errors != null) {
appLogger.w("Login failed: $errors");
if (isMpinEnabled) {
Get.toNamed('/home');
showAppSnackbar(
title: "Login Failed",
message: "Username or password is incorrect",
type: SnackbarType.error,
);
basicValidator.addErrors(errors);
basicValidator.validateForm();
basicValidator.clearErrors();
} else {
await _handleRememberMe();
appLogger.i("Login successful: ${loginData['username']}");
Get.toNamed('/home');
}
}
isLoading.value = false;
}
void handlePostLoginNavigation() {
final bool isMpinEnabled = LocalStorage.getIsMpin();
if (isMpinEnabled) {
Get.offAllNamed('/home');
} else {
Get.offAllNamed('/home');
} catch (e, stacktrace) {
appLogger.e("Exception during login", error: e, stackTrace: stacktrace);
showAppSnackbar(
title: "Login Error",
message: "An unexpected error occurred",
type: SnackbarType.error,
);
} finally {
isLoading.value = false;
}
}
@ -99,6 +99,7 @@ class LoginController extends MyController {
await LocalStorage.removeToken('username');
await LocalStorage.removeToken('password');
await LocalStorage.setBool('remember_me', false);
basicValidator.clearErrors();
}
}
@ -123,4 +124,3 @@ class LoginController extends MyController {
Get.offAndToNamed('/auth/register_account');
}
}

View File

@ -1,14 +1,13 @@
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:logger/logger.dart';
import 'package:marco/helpers/services/auth_service.dart';
import 'package:marco/helpers/services/storage/local_storage.dart';
import 'package:marco/helpers/widgets/my_form_validator.dart';
import 'package:marco/helpers/widgets/my_snackbar.dart';
import 'package:marco/view/dashboard/dashboard_screen.dart';
import 'package:marco/helpers/services/app_logger.dart';
class MPINController extends GetxController {
final Logger logger = Logger();
final MyFormValidator basicValidator = MyFormValidator();
final isNewUser = false.obs;
@ -26,11 +25,11 @@ class MPINController extends GetxController {
super.onInit();
final bool hasMpin = LocalStorage.getIsMpin();
isNewUser.value = !hasMpin;
logger.i("[MPINController] onInit called. isNewUser: ${isNewUser.value}");
appLogger.i("[MPINController] onInit called. isNewUser: ${isNewUser.value}");
}
void onDigitChanged(String value, int index, {bool isRetype = false}) {
logger.i(
appLogger.i(
"[MPINController] onDigitChanged -> index: $index, value: $value, isRetype: $isRetype");
final nodes = isRetype ? retypeFocusNodes : focusNodes;
if (value.isNotEmpty && index < 5) {
@ -41,15 +40,15 @@ class MPINController extends GetxController {
}
Future<void> onSubmitMPIN() async {
logger.i("[MPINController] onSubmitMPIN triggered");
appLogger.i("[MPINController] onSubmitMPIN triggered");
if (!formKey.currentState!.validate()) {
logger.w("[MPINController] Form validation failed");
appLogger.w("[MPINController] Form validation failed");
return;
}
final enteredMPIN = digitControllers.map((c) => c.text).join();
logger.i("[MPINController] Entered MPIN: $enteredMPIN");
appLogger.i("[MPINController] Entered MPIN: $enteredMPIN");
if (enteredMPIN.length < 6) {
_showError("Please enter all 6 digits.");
@ -58,7 +57,7 @@ class MPINController extends GetxController {
if (isNewUser.value) {
final retypeMPIN = retypeControllers.map((c) => c.text).join();
logger.i("[MPINController] Retyped MPIN: $retypeMPIN");
appLogger.i("[MPINController] Retyped MPIN: $retypeMPIN");
if (retypeMPIN.length < 6) {
_showError("Please enter all 6 digits in Retype MPIN.");
@ -72,11 +71,11 @@ class MPINController extends GetxController {
return;
}
logger.i("[MPINController] MPINs matched. Proceeding to generate MPIN.");
appLogger.i("[MPINController] MPINs matched. Proceeding to generate MPIN.");
final bool success = await generateMPIN(mpin: enteredMPIN);
if (success) {
logger.i("[MPINController] MPIN generation successful.");
appLogger.i("[MPINController] MPIN generation successful.");
showAppSnackbar(
title: "Success",
message: "MPIN generated successfully. Please login again.",
@ -84,32 +83,32 @@ class MPINController extends GetxController {
);
await LocalStorage.logout();
} else {
logger.w("[MPINController] MPIN generation failed.");
appLogger.w("[MPINController] MPIN generation failed.");
clearFields();
clearRetypeFields();
}
} else {
logger.i("[MPINController] Existing user. Proceeding to verify MPIN.");
appLogger.i("[MPINController] Existing user. Proceeding to verify MPIN.");
await verifyMPIN();
}
}
Future<void> onForgotMPIN() async {
logger.i("[MPINController] onForgotMPIN called");
appLogger.i("[MPINController] onForgotMPIN called");
isNewUser.value = true;
clearFields();
clearRetypeFields();
}
void switchToEnterMPIN() {
logger.i("[MPINController] switchToEnterMPIN called");
appLogger.i("[MPINController] switchToEnterMPIN called");
isNewUser.value = false;
clearFields();
clearRetypeFields();
}
void _showError(String message) {
logger.e("[MPINController] ERROR: $message");
appLogger.e("[MPINController] ERROR: $message");
showAppSnackbar(
title: "Error",
message: message,
@ -119,7 +118,7 @@ class MPINController extends GetxController {
void _navigateToDashboard({String? message}) {
if (message != null) {
logger
appLogger
.i("[MPINController] Navigating to Dashboard with message: $message");
showAppSnackbar(
title: "Success",
@ -131,7 +130,7 @@ class MPINController extends GetxController {
}
void clearFields() {
logger.i("[MPINController] clearFields called");
appLogger.i("[MPINController] clearFields called");
for (final c in digitControllers) {
c.clear();
}
@ -139,7 +138,7 @@ class MPINController extends GetxController {
}
void clearRetypeFields() {
logger.i("[MPINController] clearRetypeFields called");
appLogger.i("[MPINController] clearRetypeFields called");
for (final c in retypeControllers) {
c.clear();
}
@ -148,7 +147,7 @@ class MPINController extends GetxController {
@override
void onClose() {
logger.i("[MPINController] onClose called");
appLogger.i("[MPINController] onClose called");
for (final controller in digitControllers) {
controller.dispose();
}
@ -169,7 +168,7 @@ class MPINController extends GetxController {
}) async {
try {
isLoading.value = true;
logger.i("[MPINController] generateMPIN started for MPIN: $mpin");
appLogger.i("[MPINController] generateMPIN started for MPIN: $mpin");
final employeeInfo = LocalStorage.getEmployeeInfo();
final String? employeeId = employeeInfo?.id;
@ -180,7 +179,7 @@ class MPINController extends GetxController {
return false;
}
logger.i(
appLogger.i(
"[MPINController] Calling AuthService.generateMpin for employeeId: $employeeId");
final response = await AuthService.generateMpin(
@ -191,7 +190,7 @@ class MPINController extends GetxController {
isLoading.value = false;
if (response == null) {
logger.i("[MPINController] MPIN generated successfully");
appLogger.i("[MPINController] MPIN generated successfully");
showAppSnackbar(
title: "Success",
@ -203,7 +202,7 @@ class MPINController extends GetxController {
return true;
} else {
logger.w(
appLogger.w(
"[MPINController] MPIN generation returned error response: $response");
showAppSnackbar(
title: "MPIN Generation Failed",
@ -218,16 +217,16 @@ class MPINController extends GetxController {
} catch (e) {
isLoading.value = false;
_showError("Failed to generate MPIN: $e");
logger.e("[MPINController] Exception in generateMPIN: $e");
appLogger.e("[MPINController] Exception in generateMPIN: $e");
return false;
}
}
Future<void> verifyMPIN() async {
logger.i("[MPINController] verifyMPIN triggered");
appLogger.i("[MPINController] verifyMPIN triggered");
final enteredMPIN = digitControllers.map((c) => c.text).join();
logger.i("[MPINController] Entered MPIN: $enteredMPIN");
appLogger.i("[MPINController] Entered MPIN: $enteredMPIN");
if (enteredMPIN.length < 6) {
_showError("Please enter all 6 digits.");
@ -252,7 +251,7 @@ class MPINController extends GetxController {
isLoading.value = false;
if (response == null) {
logger.i("[MPINController] MPIN verified successfully.");
appLogger.i("[MPINController] MPIN verified successfully.");
// Set mpin_verified to true in local storage here:
await LocalStorage.setBool('mpin_verified', true);
@ -265,7 +264,7 @@ class MPINController extends GetxController {
_navigateToDashboard();
} else {
final errorMessage = response["error"] ?? "Invalid MPIN";
logger.w("[MPINController] MPIN verification failed: $errorMessage");
appLogger.w("[MPINController] MPIN verification failed: $errorMessage");
showAppSnackbar(
title: "Error",
message: errorMessage,
@ -277,7 +276,7 @@ class MPINController extends GetxController {
} catch (e) {
isLoading.value = false;
final error = "Failed to verify MPIN: $e";
logger.e("[MPINController] Exception in verifyMPIN: $error");
appLogger.e("[MPINController] Exception in verifyMPIN: $error");
showAppSnackbar(
title: "Error",
message: "Something went wrong. Please try again.",

View File

@ -4,6 +4,8 @@ import 'package:get/get.dart';
import 'package:marco/helpers/widgets/my_snackbar.dart';
import 'package:marco/helpers/services/auth_service.dart';
import 'package:marco/helpers/services/storage/local_storage.dart';
import 'package:marco/helpers/services/app_logger.dart';
class OTPController extends GetxController {
final formKey = GlobalKey<FormState>();
@ -23,6 +25,7 @@ class OTPController extends GetxController {
void onInit() {
super.onInit();
timer.value = 0;
appLogger.i("[OTPController] Initialized");
}
@override
@ -35,15 +38,18 @@ class OTPController extends GetxController {
for (final node in focusNodes) {
node.dispose();
}
appLogger.i("[OTPController] Disposed");
super.onClose();
}
Future<bool> _sendOTP(String email) async {
appLogger.i("[OTPController] Sending OTP to $email");
final result = await AuthService.generateOtp(email);
if (result == null) {
debugPrint('OTP sent to $email');
appLogger.i("[OTPController] OTP sent successfully to $email");
return true;
} else {
appLogger.w("[OTPController] OTP send failed: ${result['error']}");
showAppSnackbar(
title: "Error",
message: result['error'] ?? "Failed to send OTP",
@ -55,8 +61,10 @@ class OTPController extends GetxController {
Future<void> sendOTP() async {
final userEmail = emailController.text.trim();
appLogger.i("[OTPController] sendOTP called for $userEmail");
if (!_validateEmail(userEmail)) {
appLogger.w("[OTPController] Invalid email format: $userEmail");
showAppSnackbar(
title: "Error",
message: "Please enter a valid email address",
@ -81,8 +89,9 @@ class OTPController extends GetxController {
Future<void> onResendOTP() async {
if (isResending.value) return;
isResending.value = true;
appLogger.i("[OTPController] Resending OTP to ${email.value}");
isResending.value = true;
_clearOTPFields();
final success = await _sendOTP(email.value);
@ -94,6 +103,7 @@ class OTPController extends GetxController {
}
void onOTPChanged(String value, int index) {
appLogger.d("[OTPController] OTP field changed: index=$index, value=$value");
if (value.isNotEmpty) {
if (index < otpControllers.length - 1) {
focusNodes[index + 1].requestFocus();
@ -109,6 +119,7 @@ class OTPController extends GetxController {
Future<void> verifyOTP() async {
final enteredOTP = otpControllers.map((c) => c.text).join();
appLogger.i("[OTPController] Verifying OTP: $enteredOTP for email: ${email.value}");
final result = await AuthService.verifyOtp(
email: email.value,
@ -116,13 +127,14 @@ class OTPController extends GetxController {
);
if (result == null) {
appLogger.i("[OTPController] OTP verified successfully");
showAppSnackbar(
title: "Success",
message: "OTP verified successfully",
type: SnackbarType.success,
);
final bool isMpinEnabled = LocalStorage.getIsMpin();
print('MPIN Enabled? $isMpinEnabled');
appLogger.i("[OTPController] MPIN Enabled: $isMpinEnabled");
if (isMpinEnabled) {
Get.offAllNamed('/home');
@ -130,15 +142,18 @@ class OTPController extends GetxController {
Get.offAllNamed('/home');
}
} else {
final error = result['error'] ?? "Failed to verify OTP";
appLogger.w("[OTPController] OTP verification failed: $error");
showAppSnackbar(
title: "Error",
message: result['error'] ?? "Failed to verify OTP",
message: error,
type: SnackbarType.error,
);
}
}
void _clearOTPFields() {
appLogger.d("[OTPController] Clearing OTP input fields");
for (final controller in otpControllers) {
controller.clear();
}
@ -146,6 +161,7 @@ class OTPController extends GetxController {
}
void _startTimer() {
appLogger.i("[OTPController] Starting resend timer");
timer.value = 60;
_countdownTimer?.cancel();
_countdownTimer = Timer.periodic(const Duration(seconds: 1), (timer) {
@ -158,6 +174,8 @@ class OTPController extends GetxController {
}
void resetForChangeEmail() {
appLogger.i("[OTPController] Resetting OTP form for change email");
isOTPSent.value = false;
email.value = '';
emailController.clear();

View File

@ -3,16 +3,17 @@ import 'package:get/get.dart';
import 'package:marco/controller/my_controller.dart';
import 'package:marco/helpers/widgets/my_form_validator.dart';
import 'package:marco/helpers/widgets/my_validators.dart';
import 'package:marco/helpers/services/auth_service.dart';
import 'package:marco/helpers/services/auth_service.dart';
import 'package:marco/helpers/services/app_logger.dart';
class RegisterAccountController extends MyController {
MyFormValidator basicValidator = MyFormValidator();
bool showPassword = false;
@override
void onInit() {
appLogger.i("[RegisterAccountController] onInit called");
basicValidator.addField(
'email',
required: true,
@ -38,29 +39,39 @@ class RegisterAccountController extends MyController {
validators: [MyLengthValidator(min: 6, max: 10)],
controller: TextEditingController(),
);
super.onInit();
}
Future<void> onLogin() async {
if (basicValidator.validateForm()) {
update();
appLogger.i("[RegisterAccountController] Submitting registration data: ${basicValidator.getData()}");
var errors = await AuthService.loginUser(basicValidator.getData());
if (errors != null) {
appLogger.w("[RegisterAccountController] Login errors: $errors");
basicValidator.addErrors(errors);
basicValidator.validateForm();
basicValidator.clearErrors();
}
appLogger.i("[RegisterAccountController] Redirecting to /starter");
Get.toNamed('/starter');
update();
} else {
appLogger.w("[RegisterAccountController] Validation failed");
}
}
void onChangeShowPassword() {
showPassword = !showPassword;
appLogger.i("[RegisterAccountController] showPassword toggled: $showPassword");
update();
}
void gotoLogin() {
appLogger.i("[RegisterAccountController] Navigating to /auth/login-option");
Get.toNamed('/auth/login-option');
}
}

View File

@ -4,56 +4,68 @@ 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';
class ResetPasswordController extends MyController {
MyFormValidator basicValidator = MyFormValidator();
bool showPassword = false;
bool confirmPassword = false;
@override
void onInit() {
super.onInit();
appLogger.i("[ResetPasswordController] onInit called");
basicValidator.addField(
'password',
required: true,
validators: [
MyLengthValidator(min: 6, max: 10),
],
validators: [MyLengthValidator(min: 6, max: 10)],
controller: TextEditingController(),
);
basicValidator.addField(
'confirm_password',
required: true,
label: "Confirm password",
validators: [
MyLengthValidator(min: 6, max: 10),
],
validators: [MyLengthValidator(min: 6, max: 10)],
controller: TextEditingController(),
);
}
Future<void> onResetPassword() async {
appLogger.i("[ResetPasswordController] onResetPassword triggered");
if (basicValidator.validateForm()) {
final data = basicValidator.getData();
appLogger.i("[ResetPasswordController] Form data: $data");
update();
var errors = await AuthService.loginUser(basicValidator.getData());
var errors = await AuthService.loginUser(data);
if (errors != null) {
appLogger.w("[ResetPasswordController] Received errors: $errors");
basicValidator.addErrors(errors);
basicValidator.validateForm();
basicValidator.clearErrors();
}
appLogger.i("[ResetPasswordController] Navigating to /home");
Get.toNamed('/home');
update();
} else {
appLogger.w("[ResetPasswordController] Form validation failed");
}
}
void onChangeShowPassword() {
showPassword = !showPassword;
appLogger.i("[ResetPasswordController] showPassword toggled: $showPassword");
update();
}
void onConfirmPassword() {
confirmPassword = !confirmPassword;
appLogger.i("[ResetPasswordController] confirmPassword toggled: $confirmPassword");
update();
}
}
}

View File

@ -3,10 +3,10 @@ import 'package:flutter/material.dart';
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:logger/logger.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';
enum Gender {
male,
@ -16,7 +16,6 @@ enum Gender {
const Gender();
}
final Logger logger = Logger();
class AddEmployeeController extends MyController {
List<PlatformFile> files = [];
@ -67,7 +66,7 @@ class AddEmployeeController extends MyController {
@override
void onInit() {
super.onInit();
logger.i("Initializing AddEmployeeController...");
appLogger.i("Initializing AddEmployeeController...");
_initializeFields();
fetchRoles();
}
@ -91,41 +90,41 @@ class AddEmployeeController extends MyController {
required: true,
controller: TextEditingController(),
);
logger.i("Fields initialized for first_name, phone_number, last_name.");
appLogger.i("Fields initialized for first_name, phone_number, last_name.");
}
void onGenderSelected(Gender? gender) {
selectedGender = gender;
logger.i("Gender selected: ${gender?.name}");
appLogger.i("Gender selected: ${gender?.name}");
update();
}
Future<void> fetchRoles() async {
logger.i("Fetching roles...");
appLogger.i("Fetching roles...");
try {
final result = await ApiService.getRoles();
if (result != null) {
roles = List<Map<String, dynamic>>.from(result);
logger.i("Roles fetched successfully.");
appLogger.i("Roles fetched successfully.");
update();
} else {
logger.e("Failed to fetch roles: null result");
appLogger.e("Failed to fetch roles: null result");
}
} catch (e, st) {
logger.e("Error fetching roles: $e", error: e, stackTrace: st);
appLogger.e("Error fetching roles: $e", error: e, stackTrace: st);
}
}
void onRoleSelected(String? roleId) {
selectedRoleId = roleId;
logger.i("Role selected: $roleId");
appLogger.i("Role selected: $roleId");
update();
}
Future<bool> createEmployees() async {
logger.i("Starting employee creation...");
appLogger.i("Starting employee creation...");
if (selectedGender == null || selectedRoleId == null) {
logger.w("Missing gender or role.");
appLogger.w("Missing gender or role.");
showAppSnackbar(
title: "Missing Fields",
message: "Please select both Gender and Role.",
@ -139,7 +138,7 @@ class AddEmployeeController extends MyController {
final phoneNumber =
basicValidator.getController("phone_number")?.text.trim();
logger.i(
appLogger.i(
"Creating employee with Name: $firstName $lastName, Phone: $phoneNumber, Gender: ${selectedGender!.name}");
try {
@ -152,7 +151,7 @@ class AddEmployeeController extends MyController {
);
if (response == true) {
logger.i("Employee created successfully.");
appLogger.i("Employee created successfully.");
showAppSnackbar(
title: "Success",
message: "Employee created successfully!",
@ -160,10 +159,10 @@ class AddEmployeeController extends MyController {
);
return true;
} else {
logger.e("Failed to create employee (response false).");
appLogger.e("Failed to create employee (response false).");
}
} catch (e, st) {
logger.e("Error creating employee: $e", error: e, stackTrace: st);
appLogger.e("Error creating employee: $e", error: e, stackTrace: st);
}
showAppSnackbar(
@ -279,7 +278,7 @@ class AddEmployeeController extends MyController {
update();
} catch (e, st) {
logger.e("Error fetching contacts: $e", error: e, stackTrace: st);
appLogger.e("Error fetching contacts: $e", error: e, stackTrace: st);
showAppSnackbar(
title: "Error",
message: "Failed to fetch contacts.",

View File

@ -5,8 +5,7 @@ import 'package:get/get.dart';
import 'package:image_picker/image_picker.dart';
import 'package:geolocator/geolocator.dart';
import 'package:intl/intl.dart';
import 'package:logger/logger.dart';
import 'package:marco/helpers/services/app_logger.dart';
import 'package:marco/helpers/services/api_service.dart';
import 'package:marco/helpers/widgets/my_image_compressor.dart';
import 'package:marco/model/attendance_model.dart';
@ -17,8 +16,6 @@ import 'package:marco/model/regularization_log_model.dart';
import 'package:marco/model/attendance_log_view_model.dart';
import 'package:marco/controller/project_controller.dart';
final Logger log = Logger();
class AttendanceController extends GetxController {
// Data lists
List<AttendanceModel> attendances = [];
@ -61,7 +58,7 @@ class AttendanceController extends GetxController {
final today = DateTime.now();
startDateAttendance = today.subtract(const Duration(days: 7));
endDateAttendance = today.subtract(const Duration(days: 1));
log.i("Default date range set: $startDateAttendance to $endDateAttendance");
appLogger.i("Default date range set: $startDateAttendance to $endDateAttendance");
}
/// Checks and requests location permission, returns true if granted.
@ -71,13 +68,13 @@ class AttendanceController extends GetxController {
if (permission == LocationPermission.denied) {
permission = await Geolocator.requestPermission();
if (permission == LocationPermission.denied) {
log.w('Location permissions are denied');
appLogger.w('Location permissions are denied');
return false;
}
}
if (permission == LocationPermission.deniedForever) {
log.e('Location permissions are permanently denied');
appLogger.e('Location permissions are permanently denied');
return false;
}
@ -93,9 +90,9 @@ class AttendanceController extends GetxController {
if (response != null && response.isNotEmpty) {
projects = response.map((json) => ProjectModel.fromJson(json)).toList();
log.i("Projects fetched: ${projects.length}");
appLogger.i("Projects fetched: ${projects.length}");
} else {
log.e("Failed to fetch projects or no projects available.");
appLogger.e("Failed to fetch projects or no projects available.");
projects = [];
}
@ -127,7 +124,7 @@ class AttendanceController extends GetxController {
isLoading.value = false;
log.i("Project data fetched for project ID: $projectId");
appLogger.i("Project data fetched for project ID: $projectId");
}
/// Fetches employees for the given project.
@ -146,10 +143,10 @@ class AttendanceController extends GetxController {
uploadingStates[emp.id] = false.obs;
}
log.i("Employees fetched: ${employees.length} for project $projectId");
appLogger.i("Employees fetched: ${employees.length} for project $projectId");
update();
} else {
log.e("Failed to fetch employees for project $projectId");
appLogger.e("Failed to fetch employees for project $projectId");
}
isLoadingEmployees.value = false;
@ -176,7 +173,7 @@ class AttendanceController extends GetxController {
imageQuality: 80,
);
if (image == null) {
log.w("Image capture cancelled.");
appLogger.w("Image capture cancelled.");
uploadingStates[employeeId]?.value = false;
return false;
}
@ -184,7 +181,7 @@ class AttendanceController extends GetxController {
final compressedBytes =
await compressImageToUnder100KB(File(image.path));
if (compressedBytes == null) {
log.e("Image compression failed.");
appLogger.e("Image compression failed.");
uploadingStates[employeeId]?.value = false;
return false;
}
@ -221,10 +218,10 @@ class AttendanceController extends GetxController {
markTime: markTime,
);
log.i("Attendance uploaded for $employeeId, action: $action");
appLogger.i("Attendance uploaded for $employeeId, action: $action");
return result;
} catch (e, stacktrace) {
log.e("Error uploading attendance", error: e, stackTrace: stacktrace);
appLogger.e("Error uploading attendance", error: e, stackTrace: stacktrace);
return false;
} finally {
uploadingStates[employeeId]?.value = false;
@ -276,7 +273,7 @@ class AttendanceController extends GetxController {
startDateAttendance = picked.start;
endDateAttendance = picked.end;
log.i("Date range selected: $startDateAttendance to $endDateAttendance");
appLogger.i("Date range selected: $startDateAttendance to $endDateAttendance");
await controller.fetchAttendanceLogs(
Get.find<ProjectController>().selectedProject?.id,
@ -306,10 +303,10 @@ class AttendanceController extends GetxController {
if (response != null) {
attendanceLogs =
response.map((json) => AttendanceLogModel.fromJson(json)).toList();
log.i("Attendance logs fetched: ${attendanceLogs.length}");
appLogger.i("Attendance logs fetched: ${attendanceLogs.length}");
update();
} else {
log.e("Failed to fetch attendance logs for project $projectId");
appLogger.e("Failed to fetch attendance logs for project $projectId");
}
isLoadingAttendanceLogs.value = false;
@ -341,7 +338,7 @@ class AttendanceController extends GetxController {
final sortedMap =
Map<String, List<AttendanceLogModel>>.fromEntries(sortedEntries);
log.i("Logs grouped and sorted by check-in date.");
appLogger.i("Logs grouped and sorted by check-in date.");
return sortedMap;
}
@ -362,10 +359,10 @@ class AttendanceController extends GetxController {
regularizationLogs = response
.map((json) => RegularizationLogModel.fromJson(json))
.toList();
log.i("Regularization logs fetched: ${regularizationLogs.length}");
appLogger.i("Regularization logs fetched: ${regularizationLogs.length}");
update();
} else {
log.e("Failed to fetch regularization logs for project $projectId");
appLogger.e("Failed to fetch regularization logs for project $projectId");
}
isLoadingRegularizationLogs.value = false;
@ -391,10 +388,10 @@ class AttendanceController extends GetxController {
return b.activityTime!.compareTo(a.activityTime!);
});
log.i("Attendance log view fetched for ID: $id");
appLogger.i("Attendance log view fetched for ID: $id");
update();
} else {
log.e("Failed to fetch attendance log view for ID $id");
appLogger.e("Failed to fetch attendance log view for ID $id");
}
isLoadingLogView.value = false;

View File

@ -1,12 +1,10 @@
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:logger/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';
final Logger log = Logger();
class DailyTaskController extends GetxController {
List<ProjectModel> projects = [];
String? selectedProjectId;
@ -41,7 +39,7 @@ class DailyTaskController extends GetxController {
final today = DateTime.now();
startDateTask = today.subtract(const Duration(days: 7));
endDateTask = today;
log.i("Default date range set: $startDateTask to $endDateTask");
appLogger.i("Default date range set: $startDateTask to $endDateTask");
}
Future<void> fetchTaskData(String? projectId) async {
@ -73,11 +71,11 @@ class DailyTaskController extends GetxController {
// Flatten the grouped tasks into the existing dailyTasks list
dailyTasks = groupedDailyTasks.values.expand((list) => list).toList();
log.i("Daily tasks fetched and grouped: ${dailyTasks.length}");
appLogger.i("Daily tasks fetched and grouped: ${dailyTasks.length}");
update();
} else {
log.e("Failed to fetch daily tasks for project $projectId");
appLogger.e("Failed to fetch daily tasks for project $projectId");
}
}
@ -101,7 +99,7 @@ class DailyTaskController extends GetxController {
startDateTask = picked.start;
endDateTask = picked.end;
log.i("Date range selected: $startDateTask to $endDateTask");
appLogger.i("Date range selected: $startDateTask to $endDateTask");
await controller.fetchTaskData(controller.selectedProjectId);
}

View File

@ -1,10 +1,8 @@
import 'package:get/get.dart';
import 'package:logger/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';
final Logger log = Logger();
class DashboardController extends GetxController {
// Observables
final RxList<Map<String, dynamic>> roleWiseData =
@ -21,7 +19,7 @@ void onInit() {
super.onInit();
// Log to verify order of controller initialization
log.i('DashboardController initialized with project ID: ${projectController.selectedProjectId.value}');
appLogger.i('DashboardController initialized with project ID: ${projectController.selectedProjectId.value}');
if (projectController.selectedProjectId.value.isNotEmpty) {
fetchRoleWiseAttendance();
@ -30,7 +28,7 @@ void onInit() {
// React to project change
ever<String>(projectController.selectedProjectId, (id) {
if (id.isNotEmpty) {
log.i('Project changed to $id, fetching attendance');
appLogger.i('Project changed to $id, fetching attendance');
fetchRoleWiseAttendance();
}
});
@ -72,7 +70,7 @@ void onInit() {
final String projectId = projectController.selectedProjectId.value;
if (projectId.isEmpty) {
log.w('Project ID is empty, skipping API call.');
appLogger.w('Project ID is empty, skipping API call.');
return;
}
@ -85,13 +83,13 @@ void onInit() {
if (response != null) {
roleWiseData.value =
response.map((e) => Map<String, dynamic>.from(e)).toList();
log.i('Attendance overview fetched successfully.');
appLogger.i('Attendance overview fetched successfully.');
} else {
log.e('Failed to fetch attendance overview: response is null.');
appLogger.e('Failed to fetch attendance overview: response is null.');
roleWiseData.clear();
}
} catch (e, st) {
log.e('Error fetching attendance overview', error: e, stackTrace: st);
appLogger.e('Error fetching attendance overview', error: e, stackTrace: st);
roleWiseData.clear();
} finally {
isLoading.value = false;

View File

@ -1,5 +1,5 @@
import 'package:get/get.dart';
import 'package:logger/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';
final Logger log = Logger();
class EmployeesScreenController extends GetxController {
List<AttendanceModel> attendances = [];
@ -45,9 +44,9 @@ class EmployeesScreenController extends GetxController {
ApiService.getProjects,
onSuccess: (data) {
projects = data.map((json) => ProjectModel.fromJson(json)).toList();
log.i("Projects fetched: ${projects.length} projects loaded.");
appLogger.i("Projects fetched: ${projects.length} projects loaded.");
},
onEmpty: () => log.w("No project data found or API call failed."),
onEmpty: () => appLogger.w("No project data found or API call failed."),
);
isLoading.value = false;
update();
@ -55,7 +54,7 @@ class EmployeesScreenController extends GetxController {
void clearEmployees() {
employees.clear(); // Correct way to clear RxList
log.i("Employees cleared");
appLogger.i("Employees cleared");
update(['employee_screen_controller']);
}
@ -65,11 +64,11 @@ class EmployeesScreenController extends GetxController {
ApiService.getAllEmployees,
onSuccess: (data) {
employees.assignAll(data.map((json) => EmployeeModel.fromJson(json)));
log.i("All Employees fetched: ${employees.length} employees loaded.");
appLogger.i("All Employees fetched: ${employees.length} employees loaded.");
},
onEmpty: () {
employees.clear(); // Always clear on empty
log.w("No Employee data found or API call failed.");
appLogger.w("No Employee data found or API call failed.");
},
);
isLoading.value = false;
@ -78,7 +77,7 @@ class EmployeesScreenController extends GetxController {
Future<void> fetchEmployeesByProject(String? projectId) async {
if (projectId == null || projectId.isEmpty) {
log.e("Project ID is required but was null or empty.");
appLogger.e("Project ID is required but was null or empty.");
return;
}
@ -90,14 +89,14 @@ class EmployeesScreenController extends GetxController {
for (var emp in employees) {
uploadingStates[emp.id] = false.obs;
}
log.i("Employees fetched: ${employees.length} for project $projectId");
appLogger.i("Employees fetched: ${employees.length} for project $projectId");
},
onEmpty: () {
employees.clear();
log.w("No employees found for project $projectId.");
appLogger.w("No employees found for project $projectId.");
},
onError: (e) =>
log.e("Error fetching employees for project $projectId: $e"),
appLogger.e("Error fetching employees for project $projectId: $e"),
);
isLoading.value = false;
update(['employee_screen_controller']);
@ -120,7 +119,7 @@ class EmployeesScreenController extends GetxController {
if (onError != null) {
onError(e);
} else {
log.e("API call error: $e");
appLogger.e("API call error: $e");
}
}
}
@ -163,7 +162,7 @@ class EmployeesScreenController extends GetxController {
if (onError != null) {
onError(e);
} else {
log.e("API call error: $e");
appLogger.e("API call error: $e");
}
}
}

View File

@ -1,12 +1,11 @@
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:logger/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';
final Logger log = Logger();
class LayoutController extends GetxController {
// Theme Customization
@ -63,9 +62,9 @@ class LayoutController extends GetxController {
final fetchedProjects = response.map((json) => ProjectModel.fromJson(json)).toList();
projects.assignAll(fetchedProjects);
selectedProjectId = RxString(fetchedProjects.first.id.toString());
log.i("Projects fetched: ${fetchedProjects.length}");
appLogger.i("Projects fetched: ${fetchedProjects.length}");
} else {
log.w("No projects found or API call failed.");
appLogger.w("No projects found or API call failed.");
}
isLoadingProjects.value = false;

View File

@ -2,14 +2,14 @@ import 'dart:async';
import 'dart:convert';
import 'package:get/get.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'package:logger/logger.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';
final log = Logger();
class PermissionController extends GetxController {
var permissions = <UserPermission>[].obs;
@ -47,9 +47,9 @@ class PermissionController extends GetxController {
);
}
log.i("User data successfully stored in SharedPreferences.");
appLogger.i("User data successfully stored in SharedPreferences.");
} catch (e, stacktrace) {
log.e("Error storing data", error: e, stackTrace: stacktrace);
appLogger.e("Error storing data", error: e, stackTrace: stacktrace);
}
}
@ -58,7 +58,7 @@ class PermissionController extends GetxController {
if (token?.isNotEmpty ?? false) {
await loadData(token!);
} else {
log.w("No token found for loading API data.");
appLogger.w("No token found for loading API data.");
}
}
@ -67,9 +67,9 @@ class PermissionController extends GetxController {
final userData = await PermissionService.fetchAllUserData(token);
_updateState(userData);
await _storeData();
log.i("Data loaded and state updated successfully.");
appLogger.i("Data loaded and state updated successfully.");
} catch (e, stacktrace) {
log.e("Error loading data from API", error: e, stackTrace: stacktrace);
appLogger.e("Error loading data from API", error: e, stackTrace: stacktrace);
}
}
@ -78,9 +78,9 @@ class PermissionController extends GetxController {
permissions.assignAll(userData['permissions']);
employeeInfo.value = userData['employeeInfo'];
projectsInfo.assignAll(userData['projects']);
log.i("State updated with new user data.");
appLogger.i("State updated with new user data.");
} catch (e, stacktrace) {
log.e("Error updating state", error: e, stackTrace: stacktrace);
appLogger.e("Error updating state", error: e, stackTrace: stacktrace);
}
}
@ -89,34 +89,34 @@ class PermissionController extends GetxController {
final prefs = await SharedPreferences.getInstance();
return prefs.getString('jwt_token');
} catch (e, stacktrace) {
log.e("Error retrieving auth token", error: e, stackTrace: stacktrace);
appLogger.e("Error retrieving auth token", error: e, stackTrace: stacktrace);
return null;
}
}
void _startAutoRefresh() {
_refreshTimer = Timer.periodic(Duration(minutes: 30), (timer) async {
log.i("Auto-refresh triggered.");
appLogger.i("Auto-refresh triggered.");
await _loadDataFromAPI();
});
}
bool hasPermission(String permissionId) {
final hasPerm = permissions.any((p) => p.id == permissionId);
log.d("Checking permission $permissionId: $hasPerm");
appLogger.d("Checking permission $permissionId: $hasPerm");
return hasPerm;
}
bool isUserAssignedToProject(String projectId) {
final assigned = projectsInfo.any((project) => project.id == projectId);
log.d("Checking project assignment for $projectId: $assigned");
appLogger.d("Checking project assignment for $projectId: $assigned");
return assigned;
}
@override
void onClose() {
_refreshTimer?.cancel();
log.i("PermissionController disposed and timer cancelled.");
appLogger.i("PermissionController disposed and timer cancelled.");
super.onClose();
}
}

View File

@ -1,11 +1,9 @@
import 'package:get/get.dart';
import 'package:logger/logger.dart';
import 'package:marco/helpers/services/app_logger.dart';
import 'package:marco/helpers/services/api_service.dart';
import 'package:marco/model/global_project_model.dart';
import 'package:marco/helpers/services/storage/local_storage.dart';
final Logger log = Logger();
class ProjectController extends GetxController {
RxList<GlobalProjectModel> projects = <GlobalProjectModel>[].obs;
RxString selectedProjectId = ''.obs;
@ -62,9 +60,9 @@ class ProjectController extends GetxController {
}
isProjectSelectionExpanded.value = false;
log.i("Projects fetched: ${projects.length}");
appLogger.i("Projects fetched: ${projects.length}");
} else {
log.w("No projects found or API call failed.");
appLogger.w("No projects found or API call failed.");
}
isLoadingProjects.value = false;

View File

@ -1,11 +1,10 @@
import 'package:get/get.dart';
import 'package:logger/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';
final Logger log = Logger();
class AddTaskController extends GetxController {
RxMap<String, RxBool> uploadingStates = <String, RxBool>{}.obs;
@ -50,7 +49,7 @@ class AddTaskController extends GetxController {
required List<String> taskTeam,
DateTime? assignmentDate,
}) async {
logger.i("Starting assign task...");
appLogger.i("Starting assign task...");
final response = await ApiService.assignDailyTask(
workItemId: workItemId,
@ -61,7 +60,7 @@ class AddTaskController extends GetxController {
);
if (response == true) {
logger.i("Task assigned successfully.");
appLogger.i("Task assigned successfully.");
showAppSnackbar(
title: "Success",
message: "Task assigned successfully!",
@ -69,7 +68,7 @@ class AddTaskController extends GetxController {
);
return true;
} else {
logger.e("Failed to assign task.");
appLogger.e("Failed to assign task.");
showAppSnackbar(
title: "Error",
message: "Failed to assign task.",
@ -88,7 +87,7 @@ class AddTaskController extends GetxController {
required String categoryId,
DateTime? assignmentDate,
}) async {
logger.i("Creating new task...");
appLogger.i("Creating new task...");
final response = await ApiService.createTask(
parentTaskId: parentTaskId,
@ -102,7 +101,7 @@ class AddTaskController extends GetxController {
);
if (response == true) {
logger.i("Task created successfully.");
appLogger.i("Task created successfully.");
showAppSnackbar(
title: "Success",
message: "Task created successfully!",
@ -110,7 +109,7 @@ class AddTaskController extends GetxController {
);
return true;
} else {
logger.e("Failed to create task.");
appLogger.e("Failed to create task.");
showAppSnackbar(
title: "Error",
message: "Failed to create task.",
@ -138,14 +137,14 @@ class AddTaskController extends GetxController {
};
categoryIdNameMap.assignAll(mapped);
logger.i("Work categories fetched: ${dataList.length}");
appLogger.i("Work categories fetched: ${dataList.length}");
} catch (e) {
logger.e("Error parsing work categories: $e");
appLogger.e("Error parsing work categories: $e");
workMasterCategories.clear();
categoryIdNameMap.clear();
}
} else {
logger.w("No work categories found or API call failed.");
appLogger.w("No work categories found or API call failed.");
}
isLoadingWorkMasterCategories.value = false;

View File

@ -1,5 +1,5 @@
import 'package:get/get.dart';
import 'package:logger/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/dailyTaskPlaning/daily_task_planing_model.dart';
@ -7,7 +7,6 @@ import 'package:marco/model/employee_model.dart';
import 'package:marco/helpers/widgets/my_form_validator.dart';
import 'package:marco/helpers/widgets/my_snackbar.dart';
final Logger log = Logger();
class DailyTaskPlaningController extends GetxController {
List<ProjectModel> projects = [];
@ -56,20 +55,20 @@ class DailyTaskPlaningController extends GetxController {
}
Future<void> fetchRoles() async {
logger.i("Fetching roles...");
appLogger.i("Fetching roles...");
final result = await ApiService.getRoles();
if (result != null) {
roles = List<Map<String, dynamic>>.from(result);
logger.i("Roles fetched successfully.");
appLogger.i("Roles fetched successfully.");
update();
} else {
logger.e("Failed to fetch roles.");
appLogger.e("Failed to fetch roles.");
}
}
void onRoleSelected(String? roleId) {
selectedRoleId.value = roleId;
logger.i("Role selected: $roleId");
appLogger.i("Role selected: $roleId");
}
Future<bool> assignDailyTask({
@ -79,7 +78,7 @@ class DailyTaskPlaningController extends GetxController {
required List<String> taskTeam,
DateTime? assignmentDate,
}) async {
logger.i("Starting assign task...");
appLogger.i("Starting assign task...");
final response = await ApiService.assignDailyTask(
workItemId: workItemId,
@ -90,7 +89,7 @@ class DailyTaskPlaningController extends GetxController {
);
if (response == true) {
logger.i("Task assigned successfully.");
appLogger.i("Task assigned successfully.");
showAppSnackbar(
title: "Success",
message: "Task assigned successfully!",
@ -98,7 +97,7 @@ class DailyTaskPlaningController extends GetxController {
);
return true;
} else {
logger.e("Failed to assign task.");
appLogger.e("Failed to assign task.");
showAppSnackbar(
title: "Error",
message: "Failed to assign task.",
@ -115,15 +114,15 @@ class DailyTaskPlaningController extends GetxController {
final response = await ApiService.getProjects();
if (response?.isEmpty ?? true) {
log.w("No project data found or API call failed.");
appLogger.w("No project data found or API call failed.");
return;
}
projects = response!.map((json) => ProjectModel.fromJson(json)).toList();
log.i("Projects fetched: ${projects.length} projects loaded.");
appLogger.i("Projects fetched: ${projects.length} projects loaded.");
update();
} catch (e, stack) {
log.e("Error fetching projects", error: e, stackTrace: stack);
appLogger.e("Error fetching projects", error: e, stackTrace: stack);
} finally {
isLoading.value = false;
}
@ -140,16 +139,16 @@ class DailyTaskPlaningController extends GetxController {
final data = response['data'];
if (data != null) {
dailyTasks = [TaskPlanningDetailsModel.fromJson(data)];
log.i("Daily task Planning Details fetched.");
appLogger.i("Daily task Planning Details fetched.");
} else {
log.e("Data field is null");
appLogger.e("Data field is null");
}
} else {
log.e(
appLogger.e(
"Failed to fetch daily task planning Details for project $projectId");
}
} catch (e, stack) {
log.e("Error fetching daily task data", error: e, stackTrace: stack);
appLogger.e("Error fetching daily task data", error: e, stackTrace: stack);
} finally {
isLoading.value = false;
update();
@ -158,7 +157,7 @@ class DailyTaskPlaningController extends GetxController {
Future<void> fetchEmployeesByProject(String? projectId) async {
if (projectId == null || projectId.isEmpty) {
log.e("Project ID is required but was null or empty.");
appLogger.e("Project ID is required but was null or empty.");
return;
}
@ -171,13 +170,13 @@ class DailyTaskPlaningController extends GetxController {
for (var emp in employees) {
uploadingStates[emp.id] = false.obs;
}
log.i("Employees fetched: ${employees.length} for project $projectId");
appLogger.i("Employees fetched: ${employees.length} for project $projectId");
} else {
log.w("No employees found for project $projectId.");
appLogger.w("No employees found for project $projectId.");
employees = [];
}
} catch (e) {
log.e("Error fetching employees for project $projectId: $e");
appLogger.e("Error fetching employees for project $projectId: $e");
}
update();

View File

@ -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:logger/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,7 +14,6 @@ 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';
final Logger logger = Logger();
enum ApiStatus { idle, loading, success, failure }
@ -79,7 +78,7 @@ class ReportTaskActionController extends MyController {
@override
void onInit() {
super.onInit();
logger.i("Initializing ReportTaskController...");
appLogger.i("Initializing ReportTaskController...");
_initializeFormFields();
}
@ -120,11 +119,11 @@ class ReportTaskActionController extends MyController {
required String approvedTaskCount,
List<File>? images,
}) async {
logger.i("Starting task approval...");
logger.i("Project ID: $projectId");
logger.i("Comment: $comment");
logger.i("Report Action ID: $reportActionId");
logger.i("Approved Task Count: $approvedTaskCount");
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");
if (projectId.isEmpty || reportActionId.isEmpty) {
_showError("Project ID and Report Action ID are required.");
@ -172,7 +171,7 @@ class ReportTaskActionController extends MyController {
return false;
}
} catch (e) {
logger.e("Error approving task: $e");
appLogger.e("Error approving task: $e");
_showError("An error occurred.");
return false;
} finally {
@ -191,7 +190,7 @@ class ReportTaskActionController extends MyController {
required String comment,
List<File>? images,
}) async {
logger.i("Starting task comment...");
appLogger.i("Starting task comment...");
if (commentController.text.trim().isEmpty) {
_showError("Comment is required.");
@ -218,7 +217,7 @@ class ReportTaskActionController extends MyController {
_showError("Failed to comment task.");
}
} catch (e) {
logger.e("Error commenting task: $e");
appLogger.e("Error commenting task: $e");
_showError("An error occurred while commenting the task.");
} finally {
isLoading.value = false;
@ -236,7 +235,7 @@ class ReportTaskActionController extends MyController {
final model = WorkStatusResponseModel.fromJson(response);
workStatus.assignAll(model.data);
} else {
logger.w("No work statuses found or API call failed.");
appLogger.w("No work statuses found or API call failed.");
}
isLoadingWorkStatus.value = false;

View File

@ -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:logger/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,7 +12,6 @@ import 'dart:io';
import 'dart:convert';
import 'package:marco/helpers/widgets/my_image_compressor.dart';
final Logger logger = Logger();
enum ApiStatus { idle, loading, success, failure }
@ -45,7 +44,7 @@ class ReportTaskController extends MyController {
@override
void onInit() {
super.onInit();
logger.i("Initializing ReportTaskController...");
appLogger.i("Initializing ReportTaskController...");
basicValidator.addField('assigned_date',
label: "Assigned Date", controller: assignedDateController);
@ -72,7 +71,7 @@ class ReportTaskController extends MyController {
basicValidator.addField('planned_work',
label: "Planned Work", controller: plannedWorkController);
logger.i(
appLogger.i(
"Fields initialized for assigned_date, work_area, activity, team_size, assigned, completed_work, and comment.");
}
@ -100,7 +99,7 @@ class ReportTaskController extends MyController {
required DateTime reportedDate,
List<File>? images,
}) async {
logger.i("Starting task report...");
appLogger.i("Starting task report...");
final completedWork = completedWorkController.text.trim();
@ -187,7 +186,7 @@ class ReportTaskController extends MyController {
return false;
}
} catch (e) {
logger.e("Error reporting task: $e");
appLogger.e("Error reporting task: $e");
reportStatus.value = ApiStatus.failure;
showAppSnackbar(
title: "Error",
@ -225,7 +224,7 @@ class ReportTaskController extends MyController {
required String comment,
List<File>? images,
}) async {
logger.i("Starting task comment...");
appLogger.i("Starting task comment...");
final commentField = commentController.text.trim();
if (commentField.isEmpty) {
@ -268,7 +267,7 @@ class ReportTaskController extends MyController {
comment: commentField,
images: imageData,
).timeout(const Duration(seconds: 30), onTimeout: () {
logger.e("Request timed out.");
appLogger.e("Request timed out.");
throw Exception("Request timed out.");
});
@ -287,7 +286,7 @@ class ReportTaskController extends MyController {
);
}
} catch (e) {
logger.e("Error commenting task: $e");
appLogger.e("Error commenting task: $e");
showAppSnackbar(
title: "Error",
message: "An error occurred while commenting the task.",

View File

@ -3,13 +3,13 @@ import 'package:get/get.dart';
import 'package:http/http.dart' as http;
import 'package:image_picker/image_picker.dart';
import 'package:intl/intl.dart';
import 'package:logger/logger.dart';
import 'package:marco/helpers/services/auth_service.dart';
import 'package:marco/helpers/services/api_endpoints.dart';
import 'package:marco/helpers/services/storage/local_storage.dart';
import 'package:jwt_decoder/jwt_decoder.dart';
final Logger logger = Logger();
import 'package:marco/helpers/services/app_logger.dart';
class ApiService {
static const Duration timeout = Duration(seconds: 30);
static const bool enableLogs = true;
@ -21,7 +21,7 @@ class ApiService {
final token = await LocalStorage.getJwtToken();
if (token == null) {
if (enableLogs) logger.w("No JWT token found.");
if (enableLogs) appLogger.w("No JWT token found.");
return null;
}
@ -65,7 +65,7 @@ class ApiService {
};
static void _log(String message) {
if (enableLogs) logger.i(message);
if (enableLogs) appLogger.i(message);
}
static dynamic _parseResponse(http.Response response, {String label = ''}) {

View File

@ -6,9 +6,9 @@ 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:logger/logger.dart';
import 'package:marco/helpers/services/app_logger.dart';
final Logger logger = Logger();
Future<void> initializeApp() async {
setPathUrlStrategy();
@ -24,5 +24,5 @@ Future<void> initializeApp() async {
Get.put(ProjectController(), permanent: true);
AppStyle.init();
logger.i("App initialization completed successfully.");
appLogger.i("App initialization completed successfully.");
}

View File

@ -0,0 +1,107 @@
import 'dart:io';
import 'package:logger/logger.dart';
import 'package:intl/intl.dart';
import 'package:permission_handler/permission_handler.dart';
/// Global logger instance
late final Logger appLogger;
/// Log file output handler
late final FileLogOutput fileLogOutput;
/// Initialize logging (call once in `main()`)
Future<void> initLogging() async {
await requestStoragePermission();
fileLogOutput = FileLogOutput();
appLogger = Logger(
printer: SimpleFileLogPrinter(),
output: fileLogOutput,
);
}
/// Request storage permission (for Android 11+)
Future<void> requestStoragePermission() async {
final status = await Permission.manageExternalStorage.status;
if (!status.isGranted) {
await Permission.manageExternalStorage.request();
}
}
/// Custom log output that writes to a local `.txt` file
class FileLogOutput extends LogOutput {
File? _logFile;
/// Initialize log file in Downloads/marco_logs/log_YYYY-MM-DD.txt
Future<void> _init() async {
if (_logFile != null) return;
final directory = Directory('/storage/emulated/0/Download/marco_logs');
if (!await directory.exists()) {
await directory.create(recursive: true);
}
final date = DateFormat('yyyy-MM-dd').format(DateTime.now());
final filePath = '${directory.path}/log_$date.txt';
_logFile = File(filePath);
if (!await _logFile!.exists()) {
await _logFile!.create();
}
await _cleanOldLogs(directory);
}
@override
void output(OutputEvent event) async {
await _init();
final logMessage = event.lines.join('\n') + '\n';
await _logFile!.writeAsString(
logMessage,
mode: FileMode.append,
flush: true,
);
}
Future<String> getLogFilePath() async {
await _init();
return _logFile!.path;
}
Future<void> clearLogs() async {
await _init();
await _logFile!.writeAsString('');
}
Future<String> readLogs() async {
await _init();
return _logFile!.readAsString();
}
/// Delete logs older than 3 days
Future<void> _cleanOldLogs(Directory directory) async {
final files = directory.listSync();
final now = DateTime.now();
for (var file in files) {
if (file is File && file.path.endsWith('.txt')) {
final stat = await file.stat();
if (now.difference(stat.modified).inDays > 3) {
await file.delete();
}
}
}
}
}
/// A simple, readable log printer for file output
class SimpleFileLogPrinter extends LogPrinter {
@override
List<String> log(LogEvent event) {
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}' : '';
return ['[$timestamp] [$level] $message$error$stack'];
}
}

View File

@ -1,13 +1,12 @@
import 'dart:convert';
import 'package:get/get.dart';
import 'package:http/http.dart' as http;
import 'package:logger/logger.dart';
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';
final Logger logger = Logger();
import 'package:marco/helpers/services/app_logger.dart';
class AuthService {
static const String _baseUrl = ApiEndpoints.baseUrl;
@ -36,7 +35,7 @@ class AuthService {
return {"error": responseData['message'] ?? "Unexpected error occurred"};
}
} catch (e) {
logger.e("Login error: $e");
appLogger.e("Login error: $e");
return {"error": "Network error. Please check your connection."};
}
}
@ -47,7 +46,7 @@ class AuthService {
final refreshToken = await LocalStorage.getRefreshToken();
if (accessToken == null || refreshToken == null || accessToken.isEmpty || refreshToken.isEmpty) {
logger.w("Missing access/refresh token.");
appLogger.w("Missing access/refresh token.");
return false;
}
@ -68,14 +67,14 @@ class AuthService {
await LocalStorage.setJwtToken(data['data']['token']);
await LocalStorage.setRefreshToken(data['data']['refreshToken']);
await LocalStorage.setLoggedInUser(true);
logger.i("Token refreshed.");
appLogger.i("Token refreshed.");
return true;
} else {
logger.w("Refresh token failed: ${data['message']}");
appLogger.w("Refresh token failed: ${data['message']}");
return false;
}
} catch (e) {
logger.e("Token refresh error: $e");
appLogger.e("Token refresh error: $e");
return false;
}
}
@ -93,7 +92,7 @@ class AuthService {
if (response.statusCode == 200 && data['success'] == true) return null;
return {"error": data['message'] ?? "Failed to send reset link."};
} catch (e) {
logger.e("Forgot password error: $e");
appLogger.e("Forgot password error: $e");
return {"error": "Network error. Please check your connection."};
}
}
@ -111,7 +110,7 @@ class AuthService {
if (response.statusCode == 200 && data['success'] == true) return null;
return {"error": data['message'] ?? "Failed to submit demo request."};
} catch (e) {
logger.e("Request demo error: $e");
appLogger.e("Request demo error: $e");
return {"error": "Network error. Please check your connection."};
}
}
@ -130,7 +129,7 @@ class AuthService {
}
return null;
} catch (e) {
logger.e("Get industries error: $e");
appLogger.e("Get industries error: $e");
return null;
}
}
@ -156,7 +155,7 @@ class AuthService {
if (response.statusCode == 200 && data['success'] == true) return null;
return {"error": data['message'] ?? "Failed to generate MPIN."};
} catch (e) {
logger.e("Generate MPIN error: $e");
appLogger.e("Generate MPIN error: $e");
return {"error": "Network error. Please check your connection."};
}
}
@ -189,7 +188,7 @@ class AuthService {
if (response.statusCode == 200 && data['success'] == true) return null;
return {"error": data['message'] ?? "MPIN verification failed."};
} catch (e) {
logger.e("Verify MPIN error: $e");
appLogger.e("Verify MPIN error: $e");
return {"error": "Network error. Please check your connection."};
}
}
@ -207,7 +206,7 @@ class AuthService {
if (response.statusCode == 200 && data['success'] == true) return null;
return {"error": data['message'] ?? "Failed to generate OTP."};
} catch (e) {
logger.e("Generate OTP error: $e");
appLogger.e("Generate OTP error: $e");
return {"error": "Network error. Please check your connection."};
}
}
@ -231,7 +230,7 @@ class AuthService {
}
return {"error": data['message'] ?? "OTP verification failed."};
} catch (e) {
logger.e("Verify OTP error: $e");
appLogger.e("Verify OTP error: $e");
return {"error": "Network error. Please check your connection."};
}
}
@ -260,6 +259,6 @@ class AuthService {
await Get.find<ProjectController>().fetchProjects();
isLoggedIn = true;
logger.i("Login success initialized.");
appLogger.i("Login success initialized.");
}
}

View File

@ -0,0 +1,79 @@
import 'dart:io';
import 'package:logger/logger.dart';
import 'package:path_provider/path_provider.dart';
import 'package:intl/intl.dart';
class FileLogOutput extends LogOutput {
late final Directory _logDirectory;
late final String _todayLogFileName;
FileLogOutput() {
_init();
}
Future<void> _init() async {
_logDirectory = await getApplicationDocumentsDirectory();
final today = DateFormat('yyyy-MM-dd').format(DateTime.now());
_todayLogFileName = 'log_$today.txt';
await _cleanupOldLogs();
}
@override
void output(OutputEvent event) async {
final file = await _getTodayLogFile();
final logMessage = event.lines.join('\n') + '\n';
await file.writeAsString(logMessage, mode: FileMode.append, flush: true);
}
Future<File> _getTodayLogFile() async {
final path = '${_logDirectory.path}/$_todayLogFileName';
final file = File(path);
if (!await file.exists()) {
await file.create(recursive: true);
}
return file;
}
/// Keep only the most recent 3 days of logs
Future<void> _cleanupOldLogs() async {
final files = _logDirectory
.listSync()
.whereType<File>()
.where((f) => f.path.contains(RegExp(r'log_\d{4}-\d{2}-\d{2}\.txt')))
.toList();
files.sort((a, b) => b.path.compareTo(a.path));
if (files.length > 3) {
final oldFiles = files.sublist(3);
for (final f in oldFiles) {
try {
await f.delete();
} catch (e) {
}
}
}
}
/// For reading today's log
Future<String> readTodayLogs() async {
final file = await _getTodayLogFile();
return file.readAsString();
}
/// Read all log files (optional utility)
Future<Map<String, String>> readAllLogs() async {
final files = _logDirectory
.listSync()
.whereType<File>()
.where((f) => f.path.contains('log_'))
.toList();
Map<String, String> logs = {};
for (final f in files) {
logs[f.path.split('/').last] = await f.readAsString();
}
return logs;
}
}

View File

@ -1,8 +1,8 @@
import 'dart:convert';
import 'package:get/get.dart';
import 'package:http/http.dart' as http;
import 'package:logger/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,7 +10,7 @@ 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';
final Logger logger = Logger();
class PermissionService {
static final Map<String, Map<String, dynamic>> _userDataCache = {};
@ -61,7 +61,7 @@ static const String _baseUrl = ApiEndpoints.baseUrl;
final error = json.decode(response.body)['message'] ?? 'Unknown error';
throw Exception('Failed to fetch user data: $error');
} catch (e) {
logger.e('Error fetching user data: $e');
appLogger.e('Error fetching user data: $e');
rethrow;
}
}

View File

@ -3,9 +3,9 @@ import 'dart:typed_data';
import 'package:flutter_image_compress/flutter_image_compress.dart';
import 'package:path_provider/path_provider.dart';
import 'package:path/path.dart' as path;
import 'package:logger/logger.dart';
import 'package:marco/helpers/services/app_logger.dart';
final logger = Logger();
Future<Uint8List?> compressImageToUnder100KB(File file) async {
int quality = 40;
@ -24,7 +24,7 @@ Future<Uint8List?> compressImageToUnder100KB(File file) async {
);
if (result != null) {
logger.i('Quality: $quality, Size: ${(result.lengthInBytes / 1024).toStringAsFixed(2)} KB');
appLogger.i('Quality: $quality, Size: ${(result.lengthInBytes / 1024).toStringAsFixed(2)} KB');
if (result.lengthInBytes <= 100 * 1024) {
return result;

View File

@ -3,13 +3,15 @@ import 'package:marco/helpers/services/app_initializer.dart';
import 'package:marco/view/my_app.dart';
import 'package:provider/provider.dart';
import 'package:marco/helpers/theme/app_notifier.dart';
import 'package:logger/logger.dart';
final Logger logger = Logger();
import 'package:marco/helpers/services/app_logger.dart';
Future<void> main() async {
WidgetsFlutterBinding.ensureInitialized();
await initLogging();
appLogger.i("App starting...");
try {
await initializeApp();
runApp(
@ -19,7 +21,8 @@ Future<void> main() async {
),
);
} catch (e, stacktrace) {
logger.e('App failed to initialize:', error: e, stackTrace: stacktrace);
appLogger.e('App failed to initialize:', error: e, stackTrace: stacktrace);
runApp(
const MaterialApp(
home: Scaffold(

View File

@ -2,10 +2,10 @@ import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:marco/controller/task_planing/add_task_controller.dart';
import 'package:marco/helpers/widgets/my_text.dart';
import 'package:logger/logger.dart';
import 'package:marco/helpers/widgets/my_snackbar.dart';
final Logger log = Logger();
void showCreateTaskBottomSheet({
required String workArea,

View File

@ -2,8 +2,8 @@ import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:flutter_localizations/flutter_localizations.dart';
import 'package:provider/provider.dart';
import 'package:logger/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,7 +14,7 @@ import 'package:marco/helpers/theme/theme_customizer.dart';
import 'package:marco/helpers/theme/app_notifier.dart';
import 'package:marco/routes.dart';
final Logger logger = Logger();
class MyApp extends StatelessWidget {
const MyApp({super.key});
@ -22,24 +22,24 @@ class MyApp extends StatelessWidget {
Future<String> _getInitialRoute() async {
try {
if (!AuthService.isLoggedIn) {
logger.i("User not logged in. Routing to /auth/login-option");
appLogger.i("User not logged in. Routing to /auth/login-option");
return "/auth/login-option";
}
final bool hasMpin = LocalStorage.getIsMpin();
logger.i("MPIN enabled: $hasMpin");
appLogger.i("MPIN enabled: $hasMpin");
if (hasMpin) {
await LocalStorage.setBool("mpin_verified", false);
logger
appLogger
.i("Routing to /auth/mpin-auth and setting mpin_verified to false");
return "/auth/mpin-auth";
} else {
logger.i("MPIN not enabled. Routing to /home");
appLogger.i("MPIN not enabled. Routing to /home");
return "/dashboard";
}
} catch (e, stacktrace) {
logger.e("Error determining initial route",
appLogger.e("Error determining initial route",
error: e, stackTrace: stacktrace);
return "/auth/login-option";
}