refactor(logging): remove sensitive flag from logSafe calls across multiple controllers and services
This commit is contained in:
parent
e059ee71f3
commit
aac65104ab
@ -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);
|
||||
|
@ -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) {
|
||||
|
@ -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.");
|
||||
|
@ -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,
|
||||
|
@ -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();
|
||||
}
|
||||
});
|
||||
|
@ -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, );
|
||||
},
|
||||
);
|
||||
|
||||
|
@ -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);
|
||||
|
@ -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, );
|
||||
}
|
||||
}
|
||||
|
@ -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();
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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]!;
|
||||
}
|
||||
|
||||
|
@ -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',
|
||||
|
@ -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);
|
||||
|
@ -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,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
@ -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);
|
||||
|
Loading…
x
Reference in New Issue
Block a user