refactor(logging): remove sensitive flag from logSafe calls across multiple controllers and services

This commit is contained in:
Vaibhav Surve 2025-07-09 11:35:35 +05:30
parent e059ee71f3
commit aac65104ab
17 changed files with 178 additions and 237 deletions

View File

@ -32,7 +32,7 @@ class ForgotPasswordController extends MyController {
final email = data['email']?.toString() ?? '';
try {
logSafe("Forgot password requested for: $email", sensitive: true);
logSafe("Forgot password requested for: $email", );
final result = await AuthService.forgotPassword(email);
@ -50,7 +50,7 @@ class ForgotPasswordController extends MyController {
message: errorMessage,
type: SnackbarType.error,
);
logSafe("Failed to send reset password email for $email: $errorMessage", level: LogLevel.warning, sensitive: true);
logSafe("Failed to send reset password email for $email: $errorMessage", level: LogLevel.warning, );
}
} catch (e, stacktrace) {
logSafe("Error during forgot password", level: LogLevel.error, error: e, stackTrace: stacktrace);

View File

@ -55,12 +55,12 @@ class LoginController extends MyController {
try {
final loginData = basicValidator.getData();
logSafe("Attempting login for user: ${loginData['username']}", sensitive: true);
logSafe("Attempting login for user: ${loginData['username']}", );
final errors = await AuthService.loginUser(loginData);
if (errors != null) {
logSafe("Login failed for user: ${loginData['username']} with errors: $errors", level: LogLevel.warning, sensitive: true);
logSafe("Login failed for user: ${loginData['username']} with errors: $errors", level: LogLevel.warning, );
showAppSnackbar(
title: "Login Failed",
@ -73,7 +73,7 @@ class LoginController extends MyController {
basicValidator.clearErrors();
} else {
await _handleRememberMe();
logSafe("Login successful for user: ${loginData['username']}", sensitive: true);
logSafe("Login successful for user: ${loginData['username']}", );
Get.toNamed('/home');
}
} catch (e, stacktrace) {

View File

@ -29,7 +29,7 @@ class MPINController extends GetxController {
}
void onDigitChanged(String value, int index, {bool isRetype = false}) {
logSafe("onDigitChanged -> index: $index, value: $value, isRetype: $isRetype", sensitive: true);
logSafe("onDigitChanged -> index: $index, value: $value, isRetype: $isRetype", );
final nodes = isRetype ? retypeFocusNodes : focusNodes;
if (value.isNotEmpty && index < 5) {
nodes[index + 1].requestFocus();
@ -47,7 +47,7 @@ class MPINController extends GetxController {
}
final enteredMPIN = digitControllers.map((c) => c.text).join();
logSafe("Entered MPIN: $enteredMPIN", sensitive: true);
logSafe("Entered MPIN: $enteredMPIN", );
if (enteredMPIN.length < 6) {
_showError("Please enter all 6 digits.");
@ -56,7 +56,7 @@ class MPINController extends GetxController {
if (isNewUser.value) {
final retypeMPIN = retypeControllers.map((c) => c.text).join();
logSafe("Retyped MPIN: $retypeMPIN", sensitive: true);
logSafe("Retyped MPIN: $retypeMPIN", );
if (retypeMPIN.length < 6) {
_showError("Please enter all 6 digits in Retype MPIN.");
@ -177,7 +177,7 @@ class MPINController extends GetxController {
return false;
}
logSafe("Calling AuthService.generateMpin for employeeId: $employeeId", sensitive: true);
logSafe("Calling AuthService.generateMpin for employeeId: $employeeId", );
final response = await AuthService.generateMpin(
employeeId: employeeId,
@ -222,7 +222,7 @@ class MPINController extends GetxController {
logSafe("verifyMPIN triggered");
final enteredMPIN = digitControllers.map((c) => c.text).join();
logSafe("Entered MPIN: $enteredMPIN", sensitive: true);
logSafe("Entered MPIN: $enteredMPIN", );
if (enteredMPIN.length < 6) {
_showError("Please enter all 6 digits.");

View File

@ -144,7 +144,7 @@ class OTPController extends GetxController {
Get.offAllNamed('/home');
} else {
final error = result['error'] ?? "Failed to verify OTP";
logSafe("[OTPController] OTP verification failed", level: LogLevel.warning, error: error, sensitive: true);
logSafe("[OTPController] OTP verification failed", level: LogLevel.warning, error: error, );
showAppSnackbar(
title: "Error",
message: error,

View File

@ -20,7 +20,7 @@ class DashboardController extends GetxController {
logSafe(
'DashboardController initialized with project ID: ${projectController.selectedProjectId.value}',
level: LogLevel.info,
sensitive: true,
);
if (projectController.selectedProjectId.value.isNotEmpty) {
@ -30,7 +30,7 @@ class DashboardController extends GetxController {
// React to project change
ever<String>(projectController.selectedProjectId, (id) {
if (id.isNotEmpty) {
logSafe('Project changed to $id, fetching attendance', level: LogLevel.info, sensitive: true);
logSafe('Project changed to $id, fetching attendance', level: LogLevel.info, );
fetchRoleWiseAttendance();
}
});

View File

@ -106,15 +106,15 @@ class EmployeesScreenController extends GetxController {
logSafe(
"Employees fetched: ${employees.length} for project $projectId",
level: LogLevel.info,
sensitive: true,
);
},
onEmpty: () {
employees.clear();
logSafe("No employees found for project $projectId.", level: LogLevel.warning, sensitive: true);
logSafe("No employees found for project $projectId.", level: LogLevel.warning, );
},
onError: (e) {
logSafe("Error fetching employees for project $projectId", level: LogLevel.error, error: e, sensitive: true);
logSafe("Error fetching employees for project $projectId", level: LogLevel.error, error: e, );
},
);
@ -131,15 +131,15 @@ class EmployeesScreenController extends GetxController {
() => ApiService.getEmployeeDetails(employeeId),
onSuccess: (data) {
selectedEmployeeDetails.value = EmployeeDetailsModel.fromJson(data);
logSafe("Employee details loaded for $employeeId", level: LogLevel.info, sensitive: true);
logSafe("Employee details loaded for $employeeId", level: LogLevel.info, );
},
onEmpty: () {
selectedEmployeeDetails.value = null;
logSafe("No employee details found for $employeeId", level: LogLevel.warning, sensitive: true);
logSafe("No employee details found for $employeeId", level: LogLevel.warning, );
},
onError: (e) {
selectedEmployeeDetails.value = null;
logSafe("Error fetching employee details for $employeeId", level: LogLevel.error, error: e, sensitive: true);
logSafe("Error fetching employee details for $employeeId", level: LogLevel.error, error: e, );
},
);

View File

@ -76,7 +76,7 @@ class PermissionController extends GetxController {
employeeInfo.value = userData['employeeInfo'];
projectsInfo.assignAll(userData['projects']);
logSafe("State updated with new user data.", sensitive: true);
logSafe("State updated with new user data.", );
} catch (e, stacktrace) {
logSafe("Error updating state", level: LogLevel.error, error: e, stackTrace: stacktrace);
}
@ -86,7 +86,7 @@ class PermissionController extends GetxController {
try {
final prefs = await SharedPreferences.getInstance();
final token = prefs.getString('jwt_token');
logSafe("Auth token retrieved successfully.", sensitive: true);
logSafe("Auth token retrieved successfully.", );
return token;
} catch (e, stacktrace) {
logSafe("Error retrieving auth token", level: LogLevel.error, error: e, stackTrace: stacktrace);

View File

@ -147,6 +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);
logSafe("Category selected", level: LogLevel.debug, );
}
}

View File

@ -50,12 +50,12 @@ class DailyTaskPlaningController extends GetxController {
.where((e) => uploadingStates[e.id]?.value == true)
.toList();
selectedEmployees.value = selected;
logSafe("Updated selected employees", level: LogLevel.debug, sensitive: true);
logSafe("Updated selected employees", level: LogLevel.debug, );
}
void onRoleSelected(String? roleId) {
selectedRoleId.value = roleId;
logSafe("Role selected", level: LogLevel.info, sensitive: true);
logSafe("Role selected", level: LogLevel.info, );
}
Future<void> fetchRoles() async {
@ -137,7 +137,7 @@ class DailyTaskPlaningController extends GetxController {
final data = response?['data'];
if (data != null) {
dailyTasks = [TaskPlanningDetailsModel.fromJson(data)];
logSafe("Daily task Planning Details fetched", level: LogLevel.info, sensitive: true);
logSafe("Daily task Planning Details fetched", level: LogLevel.info, );
} else {
logSafe("Data field is null", level: LogLevel.warning);
}
@ -164,14 +164,14 @@ class DailyTaskPlaningController extends GetxController {
uploadingStates[emp.id] = false.obs;
}
logSafe("Employees fetched: ${employees.length} for project $projectId",
level: LogLevel.info, sensitive: true);
level: LogLevel.info, );
} else {
employees = [];
logSafe("No employees found for project $projectId", level: LogLevel.warning, sensitive: true);
logSafe("No employees found for project $projectId", level: LogLevel.warning, );
}
} catch (e, stack) {
logSafe("Error fetching employees for project $projectId",
level: LogLevel.error, error: e, stackTrace: stack, sensitive: true);
level: LogLevel.error, error: e, stackTrace: stack, );
} finally {
isLoading.value = false;
update();

View File

@ -272,18 +272,18 @@ class ReportTaskActionController extends MyController {
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);
logSafe("Image added from camera: ${pickedFile.path}", );
}
} 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);
logSafe("${pickedFiles.length} images added from gallery.", );
}
}
void removeImageAt(int index) {
if (index >= 0 && index < selectedImages.length) {
logSafe("Removing image at index $index", sensitive: true);
logSafe("Removing image at index $index", );
selectedImages.removeAt(index);
}
}

View File

@ -83,7 +83,7 @@ class ReportTaskController extends MyController {
required DateTime reportedDate,
List<File>? images,
}) async {
logSafe("Reporting task for projectId", sensitive: true);
logSafe("Reporting task for projectId", );
final completedWork = completedWorkController.text.trim();
if (completedWork.isEmpty || int.tryParse(completedWork) == null || int.parse(completedWork) < 0) {
_showError("Completed work must be a positive number.");
@ -138,7 +138,7 @@ class ReportTaskController extends MyController {
required String comment,
List<File>? images,
}) async {
logSafe("Submitting comment for project", sensitive: true);
logSafe("Submitting comment for project", );
final commentField = commentController.text.trim();
if (commentField.isEmpty) {
@ -221,7 +221,7 @@ class ReportTaskController extends MyController {
final pickedFiles = await _picker.pickMultiImage(imageQuality: 75);
selectedImages.addAll(pickedFiles.map((xfile) => File(xfile.path)));
}
logSafe("Images picked: ${selectedImages.length}", sensitive: true);
logSafe("Images picked: ${selectedImages.length}", );
} catch (e) {
logSafe("Error picking images", level: LogLevel.warning, error: e);
}

View File

@ -163,7 +163,7 @@ class ApiService {
final uri = Uri.parse("${ApiEndpoints.baseUrl}$endpoint");
logSafe("POST $uri\nHeaders: ${_headers(token)}\nBody: $body",
sensitive: true);
);
try {
final response = await http
@ -203,7 +203,7 @@ class ApiService {
if (additionalHeaders != null) ...additionalHeaders,
};
logSafe("PUT $uri\nHeaders: $headers\nBody: $body", sensitive: true);
logSafe("PUT $uri\nHeaders: $headers\nBody: $body", );
try {
final response = await http

View File

@ -19,10 +19,10 @@ class PermissionService {
String token, {
bool hasRetried = false,
}) async {
logSafe("Fetching user data...", sensitive: true);
logSafe("Fetching user data...", );
if (_userDataCache.containsKey(token)) {
logSafe("User data cache hit.", sensitive: true);
logSafe("User data cache hit.", );
return _userDataCache[token]!;
}

View File

@ -6,7 +6,7 @@ import 'package:marco/helpers/services/app_logger.dart';
class LauncherUtils {
/// Launches the phone dialer with the provided phone number
static Future<void> launchPhone(String phoneNumber) async {
logSafe('Attempting to launch phone: $phoneNumber', sensitive: true);
logSafe('Attempting to launch phone: $phoneNumber', );
final Uri url = Uri(scheme: 'tel', path: phoneNumber);
await _tryLaunch(url, 'Could not launch phone');
@ -14,7 +14,7 @@ class LauncherUtils {
/// Launches the email app with the provided email address
static Future<void> launchEmail(String email) async {
logSafe('Attempting to launch email: $email', sensitive: true);
logSafe('Attempting to launch email: $email', );
final Uri url = Uri(scheme: 'mailto', path: email);
await _tryLaunch(url, 'Could not launch email');
@ -22,17 +22,17 @@ class LauncherUtils {
/// Launches WhatsApp with the provided phone number
static Future<void> launchWhatsApp(String phoneNumber) async {
logSafe('Attempting to launch WhatsApp with: $phoneNumber', sensitive: true);
logSafe('Attempting to launch WhatsApp with: $phoneNumber', );
String normalized = phoneNumber.replaceAll(RegExp(r'\D'), '');
if (!normalized.startsWith('91')) {
normalized = '91$normalized';
}
logSafe('Normalized WhatsApp number: $normalized', sensitive: true);
logSafe('Normalized WhatsApp number: $normalized', );
if (normalized.length < 12) {
logSafe('Invalid WhatsApp number: $normalized', sensitive: true);
logSafe('Invalid WhatsApp number: $normalized', );
showAppSnackbar(
title: 'Error',
message: 'Invalid phone number for WhatsApp',
@ -62,7 +62,7 @@ class LauncherUtils {
'Failed to copy $typeLabel to clipboard: $e',
stackTrace: st,
level: LogLevel.error,
sensitive: true,
);
showAppSnackbar(
title: 'Error',

View File

@ -14,7 +14,7 @@ Future<Uint8List?> compressImageToUnder100KB(File file) async {
const int maxWidth = 800;
const int maxHeight = 800;
logSafe("Starting image compression...", sensitive: true);
logSafe("Starting image compression...", );
while (quality >= 10) {
try {
@ -59,7 +59,7 @@ Future<File> saveCompressedImageToFile(Uint8List bytes) async {
final file = File(filePath);
final savedFile = await file.writeAsBytes(bytes);
logSafe("Compressed image saved to ${savedFile.path}", sensitive: true);
logSafe("Compressed image saved to ${savedFile.path}", );
return savedFile;
} catch (e, stacktrace) {
logSafe("Error saving compressed image", level: LogLevel.error, error: e, stackTrace: stacktrace);

View File

@ -1,225 +1,166 @@
import 'package:flutter/material.dart';
import 'package:flutter_lucide/flutter_lucide.dart';
import 'package:marco/helpers/utils/mixins/ui_mixin.dart';
import 'package:marco/helpers/widgets/my_text.dart';
import 'package:marco/images.dart';
import 'package:marco/view/auth/email_login_form.dart';
import 'package:marco/view/auth/otp_login_form.dart';
import 'package:marco/helpers/services/api_endpoints.dart';
import 'package:marco/helpers/widgets/my_text.dart'; // Make sure this import is added
enum LoginOption { email, otp }
class LoginOptionScreen extends StatefulWidget {
class LoginOptionScreen extends StatelessWidget {
const LoginOptionScreen({super.key});
@override
State<LoginOptionScreen> createState() => _LoginOptionScreenState();
Widget build(BuildContext context) => const WelcomeScreen();
}
class _LoginOptionScreenState extends State<LoginOptionScreen> with UIMixin {
LoginOption _selectedOption = LoginOption.email;
class WelcomeScreen extends StatelessWidget {
const WelcomeScreen({super.key});
bool get _isBetaEnvironment => ApiEndpoints.baseUrl.contains("stage");
void _showLoginDialog(BuildContext context, LoginOption option) {
showDialog(
context: context,
builder: (_) => Dialog(
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)),
insetPadding: const EdgeInsets.all(24),
child: Padding(
padding: const EdgeInsets.all(24),
child: ConstrainedBox(
constraints: const BoxConstraints(maxWidth: 420),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
MyText(
option == LoginOption.email ? "Login with Email" : "Login with OTP",
fontSize: 20,
fontWeight: 700,
),
const SizedBox(height: 20),
option == LoginOption.email ? EmailLoginForm() : const OTPLoginScreen(),
],
),
),
),
),
);
}
@override
Widget build(BuildContext context) {
final screenWidth = MediaQuery.of(context).size.width;
return Scaffold(
backgroundColor: contentTheme.brandRed,
backgroundColor: const Color(0xFFB71C1C),
body: SafeArea(
child: LayoutBuilder(
builder: (context, constraints) {
return Column(
children: [
const SizedBox(height: 24),
_buildHeader(),
const SizedBox(height: 16),
_buildWelcomeTextsAndChips(),
const SizedBox(height: 16),
Expanded(
child: Container(
width: double.infinity,
decoration: const BoxDecoration(
child: Center(
child: SingleChildScrollView(
padding: const EdgeInsets.all(24),
child: ConstrainedBox(
constraints: BoxConstraints(maxWidth: screenWidth < 500 ? double.infinity : 420),
child: Column(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
// App Logo
Container(
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: Colors.white,
borderRadius:
BorderRadius.vertical(top: Radius.circular(32)),
borderRadius: BorderRadius.circular(20),
boxShadow: const [BoxShadow(color: Colors.black26, blurRadius: 8)],
),
child: SingleChildScrollView(
padding: const EdgeInsets.symmetric(
horizontal: 20, vertical: 24),
child: ConstrainedBox(
constraints: BoxConstraints(
minHeight: constraints.maxHeight - 200,
),
child: IntrinsicHeight(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
_buildLoginForm(),
const SizedBox(height: 24),
const SizedBox(height: 8),
Center(child: _buildVersionInfo()),
],
),
),
child: Image.asset(Images.logoDark, height: 60),
),
const SizedBox(height: 24),
// Welcome Text
MyText(
"Welcome to Marco",
fontSize: 24,
fontWeight: 800,
color: Colors.white,
textAlign: TextAlign.center,
),
const SizedBox(height: 10),
MyText(
"Streamline Project Management\nBoost Productivity with Automation.",
fontSize: 14,
color: Colors.white70,
textAlign: TextAlign.center,
),
if (_isBetaEnvironment) ...[
const SizedBox(height: 12),
Container(
padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 4),
decoration: BoxDecoration(
color: Colors.orangeAccent,
borderRadius: BorderRadius.circular(6),
),
child: MyText(
'BETA',
color: Colors.white,
fontWeight: 600,
fontSize: 12,
),
),
],
const SizedBox(height: 36),
// Login Buttons
_buildLoginButton(
context,
label: "Login with Username",
icon: LucideIcons.mail,
option: LoginOption.email,
),
),
],
);
},
const SizedBox(height: 16),
_buildLoginButton(
context,
label: "Login with OTP",
icon: LucideIcons.message_square,
option: LoginOption.otp,
),
const SizedBox(height: 36),
// Version Info
MyText(
'App version 1.0.0',
color: Colors.white60,
fontSize: 12,
),
],
),
),
),
),
),
);
}
Widget _buildHeader() {
return Container(
padding: const EdgeInsets.all(12),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(16),
boxShadow: [
BoxShadow(
color: Colors.black12,
blurRadius: 6,
offset: Offset(0, 3),
)
],
),
child: Image.asset(Images.logoDark, height: 70),
);
}
Widget _buildBetaLabel() {
return Container(
padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 4),
decoration: BoxDecoration(
color: Colors.orangeAccent,
borderRadius: BorderRadius.circular(4),
),
child: MyText(
'BETA',
color: Colors.white,
fontWeight: 600,
fontSize: 12,
),
);
}
Widget _buildLoginOptionChips() {
return Wrap(
spacing: 12,
runSpacing: 8,
alignment: WrapAlignment.center,
children: [
_buildOptionChip(
title: "User Name",
icon: LucideIcons.mail,
value: LoginOption.email,
Widget _buildLoginButton(BuildContext context,
{required String label, required IconData icon, required LoginOption option}) {
return SizedBox(
width: double.infinity,
child: ElevatedButton.icon(
icon: Icon(icon, size: 20, color: Colors.white),
label: Padding(
padding: const EdgeInsets.symmetric(vertical: 12),
child: MyText(label, fontSize: 16, fontWeight: 600, color: Colors.white),
),
_buildOptionChip(
title: "OTP",
icon: LucideIcons.message_square,
value: LoginOption.otp,
style: ElevatedButton.styleFrom(
backgroundColor: const Color(0xFFB71C1C),
side: const BorderSide(color: Colors.white70),
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
elevation: 4,
),
],
);
}
Widget _buildWelcomeTextsAndChips() {
return Padding(
padding: const EdgeInsets.symmetric(horizontal: 20),
child: Column(
children: [
MyText(
"Welcome to Marco",
fontSize: 20,
fontWeight: 700,
color: Colors.white,
textAlign: TextAlign.center,
),
const SizedBox(height: 4),
MyText(
"Streamline Project Management and Boost Productivity with Automation.",
fontSize: 14,
color: Colors.white70,
textAlign: TextAlign.center,
),
if (_isBetaEnvironment) ...[
const SizedBox(height: 8),
_buildBetaLabel(),
],
const SizedBox(height: 20),
_buildLoginOptionChips(),
],
onPressed: () => _showLoginDialog(context, option),
),
);
}
Widget _buildOptionChip({
required String title,
required IconData icon,
required LoginOption value,
}) {
final bool isSelected = _selectedOption == value;
final Color selectedTextColor = contentTheme.brandRed;
final Color unselectedTextColor = Colors.white;
final Color selectedBgColor = Colors.grey[100]!;
final Color unselectedBgColor = contentTheme.brandRed;
return ChoiceChip(
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 10),
label: Row(
mainAxisSize: MainAxisSize.min,
children: [
Icon(
icon,
size: 18,
color: isSelected ? selectedTextColor : unselectedTextColor,
),
const SizedBox(width: 6),
MyText(
title,
fontSize: 14,
fontWeight: 600,
color: isSelected ? selectedTextColor : unselectedTextColor,
),
],
),
selected: isSelected,
onSelected: (_) => setState(() => _selectedOption = value),
selectedColor: selectedBgColor,
backgroundColor: unselectedBgColor,
side: BorderSide(
color: Colors.white.withOpacity(0.6),
width: 1.2,
),
elevation: 3,
shadowColor: Colors.black12,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(15),
),
);
}
Widget _buildLoginForm() {
switch (_selectedOption) {
case LoginOption.email:
return EmailLoginForm();
case LoginOption.otp:
return const OTPLoginScreen();
}
}
Widget _buildVersionInfo() {
return Padding(
padding: const EdgeInsets.only(bottom: 8),
child: MyText(
'App version 1.0.0',
color: Colors.grey.shade500,
fontSize: 12,
),
);
}
}
}

View File

@ -25,7 +25,7 @@ class MyApp extends StatelessWidget {
}
final bool hasMpin = LocalStorage.getIsMpin();
logSafe("MPIN enabled: $hasMpin", sensitive: true);
logSafe("MPIN enabled: $hasMpin", );
if (hasMpin) {
await LocalStorage.setBool("mpin_verified", false);