Merge pull request 'configured the current user info for displaying on sidebar and topbar' (#5) from Vaibhav_Task-#136 into main
Reviewed-on: #5
This commit is contained in:
commit
1007e081b9
@ -4,28 +4,30 @@ import 'package:get/get.dart';
|
|||||||
import 'package:shared_preferences/shared_preferences.dart';
|
import 'package:shared_preferences/shared_preferences.dart';
|
||||||
import 'package:marco/helpers/services/permission_service.dart';
|
import 'package:marco/helpers/services/permission_service.dart';
|
||||||
import 'package:marco/model/user_permission.dart';
|
import 'package:marco/model/user_permission.dart';
|
||||||
|
import 'package:marco/model/employee_info.dart'; // Import the EmployeeInfo model
|
||||||
|
|
||||||
class PermissionController extends GetxController {
|
class PermissionController extends GetxController {
|
||||||
var permissions = <UserPermission>[].obs;
|
var permissions = <UserPermission>[].obs;
|
||||||
|
var employeeInfo = Rxn<EmployeeInfo>(); // Observable for employee info
|
||||||
Timer? _refreshTimer;
|
Timer? _refreshTimer;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void onInit() {
|
void onInit() {
|
||||||
super.onInit();
|
super.onInit();
|
||||||
_loadStoredPermissions(); // Try to load from local storage first
|
_loadStoredData(); // Load both permissions and employee info
|
||||||
_startAutoRefresh(); // Schedule auto-refresh every 15 minutes
|
_startAutoRefresh(); // Schedule auto-refresh every 30 minutes
|
||||||
}
|
}
|
||||||
|
|
||||||
// Load permissions from SharedPreferences
|
// Load permissions and employee info from SharedPreferences
|
||||||
Future<void> _loadStoredPermissions() async {
|
Future<void> _loadStoredData() async {
|
||||||
final prefs = await SharedPreferences.getInstance();
|
final prefs = await SharedPreferences.getInstance();
|
||||||
final storedJson = prefs.getString('user_permissions');
|
|
||||||
|
|
||||||
if (storedJson != null) {
|
// Load stored permissions
|
||||||
print("Loaded Permissions from SharedPreferences: $storedJson");
|
final storedPermissionsJson = prefs.getString('user_permissions');
|
||||||
|
if (storedPermissionsJson != null) {
|
||||||
|
print("Loaded Permissions from SharedPreferences: $storedPermissionsJson");
|
||||||
try {
|
try {
|
||||||
final List<dynamic> parsedList = jsonDecode(storedJson);
|
final List<dynamic> parsedList = jsonDecode(storedPermissionsJson);
|
||||||
|
|
||||||
permissions.assignAll(
|
permissions.assignAll(
|
||||||
parsedList
|
parsedList
|
||||||
.map((e) => UserPermission.fromJson(e as Map<String, dynamic>))
|
.map((e) => UserPermission.fromJson(e as Map<String, dynamic>))
|
||||||
@ -34,59 +36,93 @@ class PermissionController extends GetxController {
|
|||||||
} catch (e) {
|
} catch (e) {
|
||||||
print("Error decoding stored permissions: $e");
|
print("Error decoding stored permissions: $e");
|
||||||
await prefs.remove('user_permissions');
|
await prefs.remove('user_permissions');
|
||||||
await _loadPermissionsFromAPI(); // fallback to API load
|
|
||||||
}
|
}
|
||||||
} else {
|
}
|
||||||
// If no permissions stored, fallback to API
|
|
||||||
await _loadPermissionsFromAPI();
|
// Load stored employee info
|
||||||
|
final storedEmployeeInfoJson = prefs.getString('employee_info');
|
||||||
|
if (storedEmployeeInfoJson != null) {
|
||||||
|
print("Loaded Employee Info from SharedPreferences: $storedEmployeeInfoJson");
|
||||||
|
try {
|
||||||
|
final decodedEmployeeInfo = jsonDecode(storedEmployeeInfoJson);
|
||||||
|
employeeInfo.value = EmployeeInfo.fromJson(decodedEmployeeInfo);
|
||||||
|
} catch (e) {
|
||||||
|
print("Error decoding stored employee info: $e");
|
||||||
|
await prefs.remove('employee_info');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If permissions or employee info are missing, fallback to API
|
||||||
|
if (storedPermissionsJson == null || storedEmployeeInfoJson == null) {
|
||||||
|
await _loadDataFromAPI();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Save permissions to SharedPreferences
|
// Save permissions and employee info to SharedPreferences
|
||||||
Future<void> _storePermissions() async {
|
Future<void> _storeData() async {
|
||||||
final prefs = await SharedPreferences.getInstance();
|
final prefs = await SharedPreferences.getInstance();
|
||||||
final jsonList = permissions.map((e) => e.toJson()).toList();
|
|
||||||
print("Storing Permissions: $jsonList");
|
// Store permissions
|
||||||
await prefs.setString('user_permissions', jsonEncode(jsonList));
|
final permissionsJson = permissions.map((e) => e.toJson()).toList();
|
||||||
|
print("Storing Permissions: $permissionsJson");
|
||||||
|
await prefs.setString('user_permissions', jsonEncode(permissionsJson));
|
||||||
|
|
||||||
|
// Store employee info
|
||||||
|
if (employeeInfo.value != null) {
|
||||||
|
final employeeInfoJson = employeeInfo.value!.toJson();
|
||||||
|
print("Storing Employee Info: $employeeInfoJson");
|
||||||
|
await prefs.setString('employee_info', jsonEncode(employeeInfoJson));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Public method to load permissions (usually called from outside)
|
// Public method to load permissions and employee info (usually called from outside)
|
||||||
Future<void> loadPermissions(String token) async {
|
Future<void> loadData(String token) async {
|
||||||
try {
|
try {
|
||||||
final result = await PermissionService.fetchPermissions(token);
|
final result = await PermissionService.fetchPermissions(token);
|
||||||
print("Fetched Permissions from API: $result");
|
print("Fetched Permissions from API: $result");
|
||||||
|
|
||||||
permissions.assignAll(result); // Update observable list
|
permissions.assignAll(result); // Update observable list
|
||||||
await _storePermissions(); // Cache locally
|
await _storeData(); // Cache locally
|
||||||
|
|
||||||
|
// Also fetch employee info from the API (you can extend the service if needed)
|
||||||
|
await _loadEmployeeInfoFromAPI(token);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
print('Error loading permissions from API: $e');
|
print('Error loading data from API: $e');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Internal helper to load token and fetch permissions from API
|
// Internal helper to load token and fetch permissions and employee info from API
|
||||||
Future<void> _loadPermissionsFromAPI() async {
|
Future<void> _loadDataFromAPI() async {
|
||||||
final token = await _getAuthToken();
|
final token = await _getAuthToken();
|
||||||
if (token != null && token.isNotEmpty) {
|
if (token != null && token.isNotEmpty) {
|
||||||
await loadPermissions(token);
|
await loadData(token);
|
||||||
} else {
|
} else {
|
||||||
print("No token available for fetching permissions.");
|
print("No token available for fetching data.");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Retrieve token from SharedPreferences
|
// Retrieve token from SharedPreferences
|
||||||
Future<String?> _getAuthToken() async {
|
Future<String?> _getAuthToken() async {
|
||||||
final prefs = await SharedPreferences.getInstance();
|
final prefs = await SharedPreferences.getInstance();
|
||||||
return prefs.getString(
|
return prefs.getString('jwt_token'); // Or 'auth_token' if that’s the key you're using
|
||||||
'jwt_token'); // Or 'auth_token' if that’s the key you're using
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Auto-refresh every 15 minutes
|
// Auto-refresh every 30 minutes
|
||||||
void _startAutoRefresh() {
|
void _startAutoRefresh() {
|
||||||
_refreshTimer = Timer.periodic(Duration(minutes: 30), (timer) async {
|
_refreshTimer = Timer.periodic(Duration(minutes: 30), (timer) async {
|
||||||
await _loadPermissionsFromAPI();
|
await _loadDataFromAPI();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Load employee info from the API
|
||||||
|
Future<void> _loadEmployeeInfoFromAPI(String token) async {
|
||||||
|
final employeeInfoResponse = await PermissionService.fetchEmployeeInfo(token);
|
||||||
|
print("Fetched Employee Info from API: $employeeInfoResponse");
|
||||||
|
|
||||||
|
employeeInfo.value = employeeInfoResponse; // Update observable employee info
|
||||||
|
await _storeData(); // Cache employee info locally
|
||||||
|
}
|
||||||
|
|
||||||
// Check for specific permission
|
// Check for specific permission
|
||||||
bool hasPermission(String permissionId) {
|
bool hasPermission(String permissionId) {
|
||||||
return permissions.any((p) => p.id == permissionId);
|
return permissions.any((p) => p.id == permissionId);
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import 'package:http/http.dart' as http;
|
import 'package:http/http.dart' as http;
|
||||||
import 'dart:convert';
|
import 'dart:convert';
|
||||||
import 'package:marco/model/user_permission.dart';
|
import 'package:marco/model/user_permission.dart';
|
||||||
|
import 'package:marco/model/employee_info.dart';
|
||||||
|
|
||||||
class PermissionService {
|
class PermissionService {
|
||||||
static Future<List<UserPermission>> fetchPermissions(String token) async {
|
static Future<List<UserPermission>> fetchPermissions(String token) async {
|
||||||
@ -10,27 +11,12 @@ class PermissionService {
|
|||||||
headers: {'Authorization': 'Bearer $token'},
|
headers: {'Authorization': 'Bearer $token'},
|
||||||
);
|
);
|
||||||
|
|
||||||
// Print the full response for debugging
|
|
||||||
print('Status Code: ${response.statusCode}');
|
|
||||||
print('Response Body: ${response.body}');
|
|
||||||
|
|
||||||
if (response.statusCode == 200) {
|
if (response.statusCode == 200) {
|
||||||
final decoded = json.decode(response.body);
|
final decoded = json.decode(response.body);
|
||||||
|
final List<dynamic> featurePermissions = decoded['data']['featurePermissions'];
|
||||||
|
|
||||||
// Debug the decoded data
|
|
||||||
print('Decoded Data: $decoded');
|
|
||||||
|
|
||||||
// Extract featurePermissions
|
|
||||||
final List<dynamic> featurePermissions =
|
|
||||||
decoded['data']['featurePermissions'];
|
|
||||||
|
|
||||||
// Check if the featurePermissions are indeed a List of Strings
|
|
||||||
print('FeaturePermissions Type: ${featurePermissions.runtimeType}');
|
|
||||||
|
|
||||||
// Map the featurePermissions to UserPermission objects
|
|
||||||
return featurePermissions
|
return featurePermissions
|
||||||
.map<UserPermission>(
|
.map<UserPermission>((permissionId) => UserPermission.fromJson({'id': permissionId}))
|
||||||
(permissionId) => UserPermission.fromJson({'id': permissionId}))
|
|
||||||
.toList();
|
.toList();
|
||||||
} else {
|
} else {
|
||||||
final errorData = json.decode(response.body);
|
final errorData = json.decode(response.body);
|
||||||
@ -41,4 +27,27 @@ class PermissionService {
|
|||||||
throw Exception('Error fetching permissions: $e');
|
throw Exception('Error fetching permissions: $e');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// New method to fetch employee info
|
||||||
|
static Future<EmployeeInfo> fetchEmployeeInfo(String token) async {
|
||||||
|
try {
|
||||||
|
final response = await http.get(
|
||||||
|
Uri.parse('https://stageapi.marcoaiot.com/api/user/profile'),
|
||||||
|
headers: {'Authorization': 'Bearer $token'},
|
||||||
|
);
|
||||||
|
|
||||||
|
if (response.statusCode == 200) {
|
||||||
|
final decoded = json.decode(response.body);
|
||||||
|
final employeeData = decoded['data']['employeeInfo'];
|
||||||
|
|
||||||
|
return EmployeeInfo.fromJson(employeeData);
|
||||||
|
} else {
|
||||||
|
final errorData = json.decode(response.body);
|
||||||
|
throw Exception('Failed to load employee info: ${errorData['message']}');
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
print('Error fetching employee info: $e');
|
||||||
|
throw Exception('Error fetching employee info: $e');
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -3,6 +3,7 @@ import 'package:marco/helpers/services/localizations/language.dart';
|
|||||||
import 'package:marco/helpers/theme/theme_customizer.dart';
|
import 'package:marco/helpers/theme/theme_customizer.dart';
|
||||||
import 'package:shared_preferences/shared_preferences.dart';
|
import 'package:shared_preferences/shared_preferences.dart';
|
||||||
import 'package:marco/model/user_permission.dart';
|
import 'package:marco/model/user_permission.dart';
|
||||||
|
import 'package:marco/model/employee_info.dart'; // Import the EmployeeInfo model
|
||||||
import 'dart:convert';
|
import 'dart:convert';
|
||||||
class LocalStorage {
|
class LocalStorage {
|
||||||
static const String _loggedInUserKey = "user";
|
static const String _loggedInUserKey = "user";
|
||||||
@ -10,6 +11,8 @@ class LocalStorage {
|
|||||||
static const String _languageKey = "lang_code";
|
static const String _languageKey = "lang_code";
|
||||||
static const String _jwtTokenKey = "jwt_token";
|
static const String _jwtTokenKey = "jwt_token";
|
||||||
static const String _refreshTokenKey = "refresh_token";
|
static const String _refreshTokenKey = "refresh_token";
|
||||||
|
static const String _userPermissionsKey = "user_permissions";
|
||||||
|
static const String _employeeInfoKey = "employee_info";
|
||||||
|
|
||||||
static SharedPreferences? _preferencesInstance;
|
static SharedPreferences? _preferencesInstance;
|
||||||
|
|
||||||
@ -20,7 +23,6 @@ class LocalStorage {
|
|||||||
return _preferencesInstance!;
|
return _preferencesInstance!;
|
||||||
}
|
}
|
||||||
// In LocalStorage class
|
// In LocalStorage class
|
||||||
static const String _userPermissionsKey = "user_permissions";
|
|
||||||
|
|
||||||
static Future<bool> setUserPermissions(
|
static Future<bool> setUserPermissions(
|
||||||
List<UserPermission> permissions) async {
|
List<UserPermission> permissions) async {
|
||||||
@ -48,6 +50,26 @@ class LocalStorage {
|
|||||||
return preferences.remove(_userPermissionsKey);
|
return preferences.remove(_userPermissionsKey);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Store EmployeeInfo
|
||||||
|
static Future<bool> setEmployeeInfo(EmployeeInfo employeeInfo) async {
|
||||||
|
final jsonData = employeeInfo.toJson();
|
||||||
|
return preferences.setString(_employeeInfoKey, jsonEncode(jsonData));
|
||||||
|
}
|
||||||
|
|
||||||
|
static EmployeeInfo? getEmployeeInfo() {
|
||||||
|
final storedJson = preferences.getString(_employeeInfoKey);
|
||||||
|
if (storedJson != null) {
|
||||||
|
final Map<String, dynamic> json = jsonDecode(storedJson);
|
||||||
|
return EmployeeInfo.fromJson(json);
|
||||||
|
}
|
||||||
|
return null; // Return null if no employee info is found
|
||||||
|
}
|
||||||
|
|
||||||
|
static Future<bool> removeEmployeeInfo() async {
|
||||||
|
return preferences.remove(_employeeInfoKey);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Other methods for handling JWT, refresh token, etc.
|
||||||
static Future<void> init() async {
|
static Future<void> init() async {
|
||||||
_preferencesInstance = await SharedPreferences.getInstance();
|
_preferencesInstance = await SharedPreferences.getInstance();
|
||||||
await initData();
|
await initData();
|
||||||
|
40
lib/helpers/widgets/avatar.dart
Normal file
40
lib/helpers/widgets/avatar.dart
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:marco/helpers/widgets/my_container.dart';
|
||||||
|
import 'package:marco/helpers/widgets/my_text.dart';
|
||||||
|
class Avatar extends StatelessWidget {
|
||||||
|
final String firstName;
|
||||||
|
final String lastName;
|
||||||
|
final double size;
|
||||||
|
final Color backgroundColor;
|
||||||
|
final Color textColor;
|
||||||
|
|
||||||
|
// Constructor
|
||||||
|
const Avatar({
|
||||||
|
super.key,
|
||||||
|
required this.firstName,
|
||||||
|
required this.lastName,
|
||||||
|
this.size = 46.0, // Default size
|
||||||
|
this.backgroundColor = Colors.blue, // Default background color
|
||||||
|
this.textColor = Colors.white, // Default text color
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
// Extract first letters of firstName and lastName
|
||||||
|
String initials = "${firstName.isNotEmpty ? firstName[0] : ''}${lastName.isNotEmpty ? lastName[0] : ''}".toUpperCase();
|
||||||
|
|
||||||
|
return MyContainer.rounded(
|
||||||
|
height: size,
|
||||||
|
width: size,
|
||||||
|
paddingAll: 0,
|
||||||
|
color: backgroundColor, // Background color of the avatar
|
||||||
|
child: Center(
|
||||||
|
child: MyText.labelSmall(
|
||||||
|
initials,
|
||||||
|
fontWeight: 600,
|
||||||
|
color: textColor, // Text color of the initials
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
77
lib/model/employee_info.dart
Normal file
77
lib/model/employee_info.dart
Normal file
@ -0,0 +1,77 @@
|
|||||||
|
class EmployeeInfo {
|
||||||
|
final int id;
|
||||||
|
final String firstName;
|
||||||
|
final String lastName;
|
||||||
|
final String gender;
|
||||||
|
final String birthDate;
|
||||||
|
final String joiningDate;
|
||||||
|
final String currentAddress;
|
||||||
|
final String phoneNumber;
|
||||||
|
final String emergencyPhoneNumber;
|
||||||
|
final String emergencyContactPerson;
|
||||||
|
final String aadharNumber;
|
||||||
|
final bool isActive;
|
||||||
|
final String? photo; // Nullable photo
|
||||||
|
final String applicationUserId;
|
||||||
|
final int jobRoleId;
|
||||||
|
|
||||||
|
EmployeeInfo({
|
||||||
|
required this.id,
|
||||||
|
required this.firstName,
|
||||||
|
required this.lastName,
|
||||||
|
required this.gender,
|
||||||
|
required this.birthDate,
|
||||||
|
required this.joiningDate,
|
||||||
|
required this.currentAddress,
|
||||||
|
required this.phoneNumber,
|
||||||
|
required this.emergencyPhoneNumber,
|
||||||
|
required this.emergencyContactPerson,
|
||||||
|
required this.aadharNumber,
|
||||||
|
required this.isActive,
|
||||||
|
this.photo,
|
||||||
|
required this.applicationUserId,
|
||||||
|
required this.jobRoleId,
|
||||||
|
});
|
||||||
|
|
||||||
|
// Factory constructor to create an instance from JSON
|
||||||
|
factory EmployeeInfo.fromJson(Map<String, dynamic> json) {
|
||||||
|
return EmployeeInfo(
|
||||||
|
id: json['id'],
|
||||||
|
firstName: json['firstName'] ?? '',
|
||||||
|
lastName: json['lastName'] ?? '',
|
||||||
|
gender: json['gender'] ?? '',
|
||||||
|
birthDate: json['birthDate'] ?? '',
|
||||||
|
joiningDate: json['joiningDate'] ?? '',
|
||||||
|
currentAddress: json['currentAddress'] ?? '',
|
||||||
|
phoneNumber: json['phoneNumber'] ?? '',
|
||||||
|
emergencyPhoneNumber: json['emergencyPhoneNumber'] ?? '',
|
||||||
|
emergencyContactPerson: json['emergencyContactPerson'] ?? '',
|
||||||
|
aadharNumber: json['aadharNumber'] ?? '',
|
||||||
|
isActive: json['isActive'] ?? false,
|
||||||
|
photo: json['photo'], // Photo can be null
|
||||||
|
applicationUserId: json['applicationUserId'] ?? '',
|
||||||
|
jobRoleId: json['jobRoleId'] ?? 0,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert the EmployeeInfo instance to a Map (for storage or API)
|
||||||
|
Map<String, dynamic> toJson() {
|
||||||
|
return {
|
||||||
|
'id': id,
|
||||||
|
'firstName': firstName,
|
||||||
|
'lastName': lastName,
|
||||||
|
'gender': gender,
|
||||||
|
'birthDate': birthDate,
|
||||||
|
'joiningDate': joiningDate,
|
||||||
|
'currentAddress': currentAddress,
|
||||||
|
'phoneNumber': phoneNumber,
|
||||||
|
'emergencyPhoneNumber': emergencyPhoneNumber,
|
||||||
|
'emergencyContactPerson': emergencyContactPerson,
|
||||||
|
'aadharNumber': aadharNumber,
|
||||||
|
'isActive': isActive,
|
||||||
|
'photo': photo,
|
||||||
|
'applicationUserId': applicationUserId,
|
||||||
|
'jobRoleId': jobRoleId,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
@ -11,9 +11,12 @@ import 'package:marco/helpers/widgets/my_container.dart';
|
|||||||
import 'package:marco/helpers/widgets/my_spacing.dart';
|
import 'package:marco/helpers/widgets/my_spacing.dart';
|
||||||
import 'package:marco/helpers/widgets/my_text.dart';
|
import 'package:marco/helpers/widgets/my_text.dart';
|
||||||
import 'package:marco/view/layouts/layout.dart';
|
import 'package:marco/view/layouts/layout.dart';
|
||||||
|
|
||||||
class DashboardScreen extends StatelessWidget with UIMixin {
|
class DashboardScreen extends StatelessWidget with UIMixin {
|
||||||
DashboardScreen({super.key});
|
DashboardScreen({super.key});
|
||||||
|
|
||||||
|
static const String dashboardRoute = "/dashboard/attendance";
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Layout(
|
return Layout(
|
||||||
@ -34,12 +37,7 @@ class DashboardScreen extends StatelessWidget with UIMixin {
|
|||||||
Padding(
|
Padding(
|
||||||
padding: MySpacing.x(flexSpacing / 2),
|
padding: MySpacing.x(flexSpacing / 2),
|
||||||
child: Column(
|
child: Column(
|
||||||
children: _dashboardStats().map((rowStats) {
|
children: _buildDashboardStats(),
|
||||||
return Row(
|
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
|
||||||
children: rowStats,
|
|
||||||
);
|
|
||||||
}).toList(),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
@ -47,67 +45,30 @@ class DashboardScreen extends StatelessWidget with UIMixin {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
List<List<Widget>> _dashboardStats() {
|
List<Widget> _buildDashboardStats() {
|
||||||
final stats = [
|
final stats = [
|
||||||
{
|
_StatItem(LucideIcons.layout_dashboard, "Dashboard", contentTheme.primary),
|
||||||
"icon": LucideIcons.layout_dashboard,
|
_StatItem(LucideIcons.folder, "Projects", contentTheme.secondary),
|
||||||
"title": "Dashboard",
|
_StatItem(LucideIcons.check, "Attendance", contentTheme.success),
|
||||||
"route": "/dashboard/attendance",
|
_StatItem(LucideIcons.users, "Task", contentTheme.info),
|
||||||
"color": contentTheme.primary
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"icon": LucideIcons.folder,
|
|
||||||
"title": "Projects",
|
|
||||||
"route": "/dashboard/attendance",
|
|
||||||
"color": contentTheme.secondary
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"icon": LucideIcons.check,
|
|
||||||
"title": "Attendence",
|
|
||||||
"route": "/dashboard/attendance",
|
|
||||||
"color": contentTheme.success
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"icon": LucideIcons.users,
|
|
||||||
"title": "Task",
|
|
||||||
"route": "/dashboard/attendance",
|
|
||||||
"color": contentTheme.info
|
|
||||||
},
|
|
||||||
];
|
];
|
||||||
|
|
||||||
// Group the stats into pairs for rows
|
return List.generate(
|
||||||
List<List<Widget>> rows = [];
|
(stats.length / 2).ceil(),
|
||||||
for (int i = 0; i < stats.length; i += 2) {
|
(index) => Row(
|
||||||
rows.add([
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
_buildStatCard(
|
children: [
|
||||||
icon: stats[i]['icon'] as IconData,
|
_buildStatCard(stats[index * 2]),
|
||||||
title: stats[i]['title'] as String,
|
if (index * 2 + 1 < stats.length) _buildStatCard(stats[index * 2 + 1]),
|
||||||
route: stats[i]['route'] as String,
|
],
|
||||||
color: stats[i]['color'] as Color,
|
),
|
||||||
),
|
);
|
||||||
if (i + 1 < stats.length) _buildStatCard(
|
|
||||||
icon: stats[i + 1]['icon'] as IconData,
|
|
||||||
title: stats[i + 1]['title'] as String,
|
|
||||||
route: stats[i + 1]['route'] as String,
|
|
||||||
color: stats[i + 1]['color'] as Color,
|
|
||||||
),
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
return rows;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildStatCard({
|
Widget _buildStatCard(_StatItem statItem) {
|
||||||
required IconData icon,
|
|
||||||
required String title,
|
|
||||||
required String route,
|
|
||||||
required Color color,
|
|
||||||
String count = "",
|
|
||||||
String change = "",
|
|
||||||
}) {
|
|
||||||
return Expanded(
|
return Expanded(
|
||||||
child: InkWell(
|
child: InkWell(
|
||||||
onTap: () => Get.toNamed(route),
|
onTap: () => Get.toNamed(dashboardRoute),
|
||||||
child: MyCard.bordered(
|
child: MyCard.bordered(
|
||||||
borderRadiusAll: 10,
|
borderRadiusAll: 10,
|
||||||
border: Border.all(color: Colors.grey.withOpacity(0.2)),
|
border: Border.all(color: Colors.grey.withOpacity(0.2)),
|
||||||
@ -117,39 +78,33 @@ class DashboardScreen extends StatelessWidget with UIMixin {
|
|||||||
child: Column(
|
child: Column(
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
children: [
|
children: [
|
||||||
MyContainer(
|
_buildStatCardIcon(statItem),
|
||||||
paddingAll: 16,
|
|
||||||
color: color.withOpacity(0.2),
|
|
||||||
child: MyContainer(
|
|
||||||
paddingAll: 8,
|
|
||||||
color: color,
|
|
||||||
child: Icon(icon, size: 16, color: contentTheme.light),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
MySpacing.height(12),
|
MySpacing.height(12),
|
||||||
MyText.labelSmall(title, maxLines: 1),
|
MyText.labelSmall(statItem.title, maxLines: 1),
|
||||||
if (count.isNotEmpty)
|
|
||||||
MyText.bodyMedium(count, fontWeight: 600, maxLines: 1),
|
|
||||||
if (change.isNotEmpty)
|
|
||||||
Padding(
|
|
||||||
padding: MySpacing.top(8),
|
|
||||||
child: MyContainer(
|
|
||||||
padding: MySpacing.xy(6, 4),
|
|
||||||
color: change.startsWith('+')
|
|
||||||
? Colors.green.withOpacity(0.2)
|
|
||||||
: theme.colorScheme.error.withOpacity(0.2),
|
|
||||||
child: MyText.labelSmall(
|
|
||||||
change,
|
|
||||||
color: change.startsWith('+')
|
|
||||||
? Colors.green
|
|
||||||
: theme.colorScheme.error,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Widget _buildStatCardIcon(_StatItem statItem) {
|
||||||
|
return MyContainer(
|
||||||
|
paddingAll: 16,
|
||||||
|
color: statItem.color.withOpacity(0.2),
|
||||||
|
child: MyContainer(
|
||||||
|
paddingAll: 8,
|
||||||
|
color: statItem.color,
|
||||||
|
child: Icon(statItem.icon, size: 16, color: contentTheme.light),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class _StatItem {
|
||||||
|
final IconData icon;
|
||||||
|
final String title;
|
||||||
|
final Color color;
|
||||||
|
|
||||||
|
_StatItem(this.icon, this.title, this.color);
|
||||||
}
|
}
|
||||||
|
@ -9,13 +9,15 @@ import 'package:marco/helpers/widgets/my_dashed_divider.dart';
|
|||||||
import 'package:marco/helpers/widgets/my_responsive.dart';
|
import 'package:marco/helpers/widgets/my_responsive.dart';
|
||||||
import 'package:marco/helpers/widgets/my_spacing.dart';
|
import 'package:marco/helpers/widgets/my_spacing.dart';
|
||||||
import 'package:marco/helpers/widgets/my_text.dart';
|
import 'package:marco/helpers/widgets/my_text.dart';
|
||||||
import 'package:marco/images.dart';
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:get/get.dart';
|
import 'package:get/get.dart';
|
||||||
import 'package:marco/view/layouts/left_bar.dart';
|
import 'package:marco/view/layouts/left_bar.dart';
|
||||||
import 'package:marco/view/layouts/right_bar.dart';
|
import 'package:marco/view/layouts/right_bar.dart';
|
||||||
import 'package:marco/view/layouts/top_bar.dart';
|
import 'package:marco/view/layouts/top_bar.dart';
|
||||||
import 'package:marco/widgets/custom_pop_menu.dart';
|
import 'package:marco/widgets/custom_pop_menu.dart';
|
||||||
|
import 'package:marco/helpers/services/storage/local_storage.dart';
|
||||||
|
import 'package:marco/model/employee_info.dart';
|
||||||
|
import 'package:marco/helpers/widgets/avatar.dart';
|
||||||
|
|
||||||
class Layout extends StatelessWidget {
|
class Layout extends StatelessWidget {
|
||||||
final Widget? child;
|
final Widget? child;
|
||||||
@ -23,6 +25,7 @@ class Layout extends StatelessWidget {
|
|||||||
final LayoutController controller = LayoutController();
|
final LayoutController controller = LayoutController();
|
||||||
final topBarTheme = AdminTheme.theme.topBarTheme;
|
final topBarTheme = AdminTheme.theme.topBarTheme;
|
||||||
final contentTheme = AdminTheme.theme.contentTheme;
|
final contentTheme = AdminTheme.theme.contentTheme;
|
||||||
|
final EmployeeInfo? employeeInfo = LocalStorage.getEmployeeInfo();
|
||||||
|
|
||||||
Layout({super.key, this.child});
|
Layout({super.key, this.child});
|
||||||
|
|
||||||
@ -30,13 +33,13 @@ class Layout extends StatelessWidget {
|
|||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return MyResponsive(builder: (BuildContext context, _, screenMT) {
|
return MyResponsive(builder: (BuildContext context, _, screenMT) {
|
||||||
return GetBuilder(
|
return GetBuilder(
|
||||||
init: controller,
|
init: controller,
|
||||||
builder: (controller) {
|
builder: (controller) {
|
||||||
if (screenMT.isMobile || screenMT.isTablet) {
|
if (screenMT.isMobile || screenMT.isTablet) {
|
||||||
return mobileScreen();
|
return mobileScreen();
|
||||||
} else {
|
} else {
|
||||||
return largeScreen();
|
return largeScreen();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -47,16 +50,6 @@ class Layout extends StatelessWidget {
|
|||||||
appBar: AppBar(
|
appBar: AppBar(
|
||||||
elevation: 0,
|
elevation: 0,
|
||||||
actions: [
|
actions: [
|
||||||
InkWell(
|
|
||||||
onTap: () {
|
|
||||||
ThemeCustomizer.setTheme(ThemeCustomizer.instance.theme == ThemeMode.dark ? ThemeMode.light : ThemeMode.dark);
|
|
||||||
},
|
|
||||||
child: Icon(
|
|
||||||
ThemeCustomizer.instance.theme == ThemeMode.dark ? LucideIcons.sun : LucideIcons.moon,
|
|
||||||
size: 18,
|
|
||||||
color: topBarTheme.onBackground,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
MySpacing.width(8),
|
MySpacing.width(8),
|
||||||
CustomPopupMenu(
|
CustomPopupMenu(
|
||||||
backdrop: true,
|
backdrop: true,
|
||||||
@ -82,13 +75,12 @@ class Layout extends StatelessWidget {
|
|||||||
menu: Padding(
|
menu: Padding(
|
||||||
padding: MySpacing.xy(8, 8),
|
padding: MySpacing.xy(8, 8),
|
||||||
child: MyContainer.rounded(
|
child: MyContainer.rounded(
|
||||||
paddingAll: 0,
|
paddingAll: 0,
|
||||||
child: Image.asset(
|
child: Avatar(
|
||||||
Images.avatars[0],
|
firstName: employeeInfo?.firstName ?? 'First',
|
||||||
height: 28,
|
lastName: employeeInfo?.lastName ?? 'Name',
|
||||||
width: 28,
|
),
|
||||||
fit: BoxFit.cover,
|
),
|
||||||
)),
|
|
||||||
),
|
),
|
||||||
menuBuilder: (_) => buildAccountMenu(),
|
menuBuilder: (_) => buildAccountMenu(),
|
||||||
),
|
),
|
||||||
@ -111,21 +103,21 @@ class Layout extends StatelessWidget {
|
|||||||
children: [
|
children: [
|
||||||
LeftBar(isCondensed: ThemeCustomizer.instance.leftBarCondensed),
|
LeftBar(isCondensed: ThemeCustomizer.instance.leftBarCondensed),
|
||||||
Expanded(
|
Expanded(
|
||||||
child: Stack(
|
child: Stack(
|
||||||
children: [
|
children: [
|
||||||
Positioned(
|
Positioned(
|
||||||
top: 0,
|
top: 0,
|
||||||
right: 0,
|
right: 0,
|
||||||
left: 0,
|
left: 0,
|
||||||
bottom: 0,
|
bottom: 0,
|
||||||
child: SingleChildScrollView(
|
child: SingleChildScrollView(
|
||||||
padding: MySpacing.fromLTRB(0, 58 + flexSpacing, 0, flexSpacing),
|
padding: MySpacing.fromLTRB(0, 58 + flexSpacing, 0, flexSpacing),
|
||||||
key: controller.scrollKey,
|
key: controller.scrollKey,
|
||||||
child: child,
|
child: child,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
Positioned(top: 0, left: 0, right: 0, child: TopBar()),
|
||||||
Positioned(top: 0, left: 0, right: 0, child: TopBar()),
|
],
|
||||||
],
|
|
||||||
)),
|
)),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
@ -156,9 +148,9 @@ class Layout extends StatelessWidget {
|
|||||||
child: Column(
|
child: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
buildNotification("Your order is received", "Order #1232 is ready to deliver"),
|
buildNotification("Welcome to Marco", "Welcome to Marco, we are glad to have you here"),
|
||||||
MySpacing.height(12),
|
MySpacing.height(12),
|
||||||
buildNotification("Account Security ", "Your account password changed 1 hour ago"),
|
buildNotification("New update available", "There is a new update available for your app"),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@ -256,32 +248,6 @@ class Layout extends StatelessWidget {
|
|||||||
height: 1,
|
height: 1,
|
||||||
thickness: 1,
|
thickness: 1,
|
||||||
),
|
),
|
||||||
Padding(
|
|
||||||
padding: MySpacing.xy(8, 8),
|
|
||||||
child: MyButton(
|
|
||||||
tapTargetSize: MaterialTapTargetSize.shrinkWrap,
|
|
||||||
onPressed: () => {},
|
|
||||||
borderRadiusAll: AppStyle.buttonRadius.medium,
|
|
||||||
padding: MySpacing.xy(8, 4),
|
|
||||||
splashColor: contentTheme.danger.withAlpha(28),
|
|
||||||
backgroundColor: Colors.transparent,
|
|
||||||
child: Row(
|
|
||||||
children: [
|
|
||||||
Icon(
|
|
||||||
LucideIcons.log_out,
|
|
||||||
size: 14,
|
|
||||||
color: contentTheme.danger,
|
|
||||||
),
|
|
||||||
MySpacing.width(8),
|
|
||||||
MyText.labelMedium(
|
|
||||||
"Log out",
|
|
||||||
fontWeight: 600,
|
|
||||||
color: contentTheme.danger,
|
|
||||||
)
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
// All import statements remain unchanged
|
||||||
import 'package:marco/helpers/theme/theme_customizer.dart';
|
import 'package:marco/helpers/theme/theme_customizer.dart';
|
||||||
import 'package:marco/helpers/services/url_service.dart';
|
import 'package:marco/helpers/services/url_service.dart';
|
||||||
import 'package:marco/helpers/utils/mixins/ui_mixin.dart';
|
import 'package:marco/helpers/utils/mixins/ui_mixin.dart';
|
||||||
@ -11,6 +12,9 @@ import 'package:marco/widgets/custom_pop_menu.dart';
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:get/route_manager.dart';
|
import 'package:get/route_manager.dart';
|
||||||
import 'package:flutter_lucide/flutter_lucide.dart';
|
import 'package:flutter_lucide/flutter_lucide.dart';
|
||||||
|
import 'package:marco/helpers/services/storage/local_storage.dart';
|
||||||
|
import 'package:marco/model/employee_info.dart';
|
||||||
|
import 'package:marco/helpers/widgets/avatar.dart';
|
||||||
|
|
||||||
typedef LeftbarMenuFunction = void Function(String key);
|
typedef LeftbarMenuFunction = void Function(String key);
|
||||||
|
|
||||||
@ -41,15 +45,18 @@ class LeftBar extends StatefulWidget {
|
|||||||
_LeftBarState createState() => _LeftBarState();
|
_LeftBarState createState() => _LeftBarState();
|
||||||
}
|
}
|
||||||
|
|
||||||
class _LeftBarState extends State<LeftBar> with SingleTickerProviderStateMixin, UIMixin {
|
class _LeftBarState extends State<LeftBar>
|
||||||
|
with SingleTickerProviderStateMixin, UIMixin {
|
||||||
final ThemeCustomizer customizer = ThemeCustomizer.instance;
|
final ThemeCustomizer customizer = ThemeCustomizer.instance;
|
||||||
|
|
||||||
bool isCondensed = false;
|
bool isCondensed = false;
|
||||||
String path = UrlService.getCurrentUrl();
|
String path = UrlService.getCurrentUrl();
|
||||||
|
EmployeeInfo? employeeInfo;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
|
employeeInfo = LocalStorage.getEmployeeInfo();
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@ -68,75 +75,93 @@ class _LeftBarState extends State<LeftBar> with SingleTickerProviderStateMixin,
|
|||||||
children: [
|
children: [
|
||||||
Center(
|
Center(
|
||||||
child: Padding(
|
child: Padding(
|
||||||
padding: MySpacing.y(13),
|
padding: MySpacing.y(12),
|
||||||
child: InkWell(
|
child: InkWell(
|
||||||
onTap: () => Get.toNamed('/home'),
|
onTap: () => Get.toNamed('/home'),
|
||||||
child: Image.asset(
|
child: Image.asset(
|
||||||
(ThemeCustomizer.instance.theme == ThemeMode.light
|
(ThemeCustomizer.instance.theme == ThemeMode.light
|
||||||
? (widget.isCondensed ? Images.logoLightSmall : Images.logoLight)
|
? (widget.isCondensed
|
||||||
: (widget.isCondensed ? Images.logoDarkSmall : Images.logoDark)),
|
? Images.logoLightSmall
|
||||||
height: 28,
|
: Images.logoLight)
|
||||||
|
: (widget.isCondensed
|
||||||
|
? Images.logoDarkSmall
|
||||||
|
: Images.logoDark)),
|
||||||
|
height: 60,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
Expanded(
|
Expanded(
|
||||||
child: ScrollConfiguration(
|
child: ScrollConfiguration(
|
||||||
behavior: ScrollConfiguration.of(context).copyWith(scrollbars: false),
|
behavior:
|
||||||
child: ListView(
|
ScrollConfiguration.of(context).copyWith(scrollbars: false),
|
||||||
shrinkWrap: true,
|
child: ListView(
|
||||||
controller: ScrollController(),
|
shrinkWrap: true,
|
||||||
physics: BouncingScrollPhysics(),
|
controller: ScrollController(),
|
||||||
clipBehavior: Clip.antiAliasWithSaveLayer,
|
physics: BouncingScrollPhysics(),
|
||||||
children: [
|
clipBehavior: Clip.antiAliasWithSaveLayer,
|
||||||
Divider(),
|
|
||||||
labelWidget("Dashboard"),
|
|
||||||
NavigationItem(iconData: LucideIcons.layout_dashboard, title: "Dashboard", isCondensed: isCondensed, route: '/dashboard'),
|
|
||||||
NavigationItem(iconData: LucideIcons.layout_template, title: "Attendance", isCondensed: isCondensed, route: '/dashboard/attendance'),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
)),
|
|
||||||
if (!isCondensed) Divider(),
|
|
||||||
if (!isCondensed)
|
|
||||||
MyContainer.transparent(
|
|
||||||
paddingAll: 12,
|
|
||||||
child: Row(
|
|
||||||
children: [
|
children: [
|
||||||
MyContainer.rounded(
|
Divider(),
|
||||||
height: 46,
|
labelWidget("Dashboard"),
|
||||||
width: 46,
|
NavigationItem(
|
||||||
paddingAll: 0,
|
iconData: LucideIcons.layout_dashboard,
|
||||||
child: Image.asset(Images.avatars[0]),
|
title: "Dashboard",
|
||||||
),
|
isCondensed: isCondensed,
|
||||||
MySpacing.width(16),
|
route: '/dashboard'),
|
||||||
Expanded(
|
NavigationItem(
|
||||||
child: Column(
|
iconData: LucideIcons.layout_template,
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
title: "Attendance",
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
isCondensed: isCondensed,
|
||||||
children: [
|
route: '/dashboard/attendance'),
|
||||||
MyText.labelMedium("Jonathan", fontWeight: 600),
|
|
||||||
MySpacing.height(8),
|
|
||||||
MyText.labelSmall("jonathan@gmail.com", fontWeight: 600, muted: true),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
MyContainer(
|
|
||||||
onTap: () {
|
|
||||||
Get.toNamed('/auth/login');
|
|
||||||
},
|
|
||||||
color: leftBarTheme.activeItemBackground,
|
|
||||||
paddingAll: 8,
|
|
||||||
child: Icon(LucideIcons.log_out, size: 16, color: leftBarTheme.activeItemColor),
|
|
||||||
)
|
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
),
|
||||||
|
Divider(),
|
||||||
|
if (!isCondensed) userInfoSection(),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Widget userInfoSection() {
|
||||||
|
return Padding(
|
||||||
|
padding: MySpacing.fromLTRB(16, 8, 16, 8),
|
||||||
|
child: Row(
|
||||||
|
children: [
|
||||||
|
Avatar(
|
||||||
|
firstName: employeeInfo?.firstName ?? 'First',
|
||||||
|
lastName: employeeInfo?.lastName ?? 'Name',
|
||||||
|
),
|
||||||
|
MySpacing.width(16),
|
||||||
|
Expanded(
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
MyText.labelSmall(
|
||||||
|
"${employeeInfo?.firstName ?? 'First Name'} ${employeeInfo?.lastName ?? 'Last Name'}",
|
||||||
|
fontWeight: 600,
|
||||||
|
muted: true,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
MyContainer(
|
||||||
|
onTap: () {
|
||||||
|
Get.offNamed('/auth/login');
|
||||||
|
},
|
||||||
|
color: leftBarTheme.activeItemBackground,
|
||||||
|
paddingAll: 8,
|
||||||
|
child: Icon(LucideIcons.log_out,
|
||||||
|
size: 16, color: leftBarTheme.activeItemColor),
|
||||||
|
)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
Widget labelWidget(String label) {
|
Widget labelWidget(String label) {
|
||||||
return isCondensed
|
return isCondensed
|
||||||
? MySpacing.empty()
|
? MySpacing.empty()
|
||||||
@ -161,13 +186,20 @@ class MenuWidget extends StatefulWidget {
|
|||||||
final bool active;
|
final bool active;
|
||||||
final List<MenuItem> children;
|
final List<MenuItem> children;
|
||||||
|
|
||||||
const MenuWidget({super.key, required this.iconData, required this.title, this.isCondensed = false, this.active = false, this.children = const []});
|
const MenuWidget(
|
||||||
|
{super.key,
|
||||||
|
required this.iconData,
|
||||||
|
required this.title,
|
||||||
|
this.isCondensed = false,
|
||||||
|
this.active = false,
|
||||||
|
this.children = const []});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
_MenuWidgetState createState() => _MenuWidgetState();
|
_MenuWidgetState createState() => _MenuWidgetState();
|
||||||
}
|
}
|
||||||
|
|
||||||
class _MenuWidgetState extends State<MenuWidget> with UIMixin, SingleTickerProviderStateMixin {
|
class _MenuWidgetState extends State<MenuWidget>
|
||||||
|
with UIMixin, SingleTickerProviderStateMixin {
|
||||||
bool isHover = false;
|
bool isHover = false;
|
||||||
bool isActive = false;
|
bool isActive = false;
|
||||||
late Animation<double> _iconTurns;
|
late Animation<double> _iconTurns;
|
||||||
@ -178,8 +210,10 @@ class _MenuWidgetState extends State<MenuWidget> with UIMixin, SingleTickerProvi
|
|||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
_controller = AnimationController(duration: Duration(milliseconds: 200), vsync: this);
|
_controller =
|
||||||
_iconTurns = _controller.drive(Tween<double>(begin: 0.0, end: 0.5).chain(CurveTween(curve: Curves.easeIn)));
|
AnimationController(duration: Duration(milliseconds: 200), vsync: this);
|
||||||
|
_iconTurns = _controller.drive(Tween<double>(begin: 0.0, end: 0.5)
|
||||||
|
.chain(CurveTween(curve: Curves.easeIn)));
|
||||||
LeftbarObserver.attachListener(widget.title, onChangeMenuActive);
|
LeftbarObserver.attachListener(widget.title, onChangeMenuActive);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -241,12 +275,16 @@ class _MenuWidgetState extends State<MenuWidget> with UIMixin, SingleTickerProvi
|
|||||||
},
|
},
|
||||||
child: MyContainer.transparent(
|
child: MyContainer.transparent(
|
||||||
margin: MySpacing.fromLTRB(16, 0, 16, 8),
|
margin: MySpacing.fromLTRB(16, 0, 16, 8),
|
||||||
color: isActive || isHover ? leftBarTheme.activeItemBackground : Colors.transparent,
|
color: isActive || isHover
|
||||||
|
? leftBarTheme.activeItemBackground
|
||||||
|
: Colors.transparent,
|
||||||
padding: MySpacing.xy(8, 8),
|
padding: MySpacing.xy(8, 8),
|
||||||
child: Center(
|
child: Center(
|
||||||
child: Icon(
|
child: Icon(
|
||||||
widget.iconData,
|
widget.iconData,
|
||||||
color: (isHover || isActive) ? leftBarTheme.activeItemColor : leftBarTheme.onBackground,
|
color: (isHover || isActive)
|
||||||
|
? leftBarTheme.activeItemColor
|
||||||
|
: leftBarTheme.onBackground,
|
||||||
size: 20,
|
size: 20,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@ -308,7 +346,9 @@ class _MenuWidgetState extends State<MenuWidget> with UIMixin, SingleTickerProvi
|
|||||||
Icon(
|
Icon(
|
||||||
widget.iconData,
|
widget.iconData,
|
||||||
size: 20,
|
size: 20,
|
||||||
color: isHover || isActive ? leftBarTheme.activeItemColor : leftBarTheme.onBackground,
|
color: isHover || isActive
|
||||||
|
? leftBarTheme.activeItemColor
|
||||||
|
: leftBarTheme.onBackground,
|
||||||
),
|
),
|
||||||
MySpacing.width(18),
|
MySpacing.width(18),
|
||||||
Expanded(
|
Expanded(
|
||||||
@ -317,7 +357,9 @@ class _MenuWidgetState extends State<MenuWidget> with UIMixin, SingleTickerProvi
|
|||||||
maxLines: 1,
|
maxLines: 1,
|
||||||
overflow: TextOverflow.ellipsis,
|
overflow: TextOverflow.ellipsis,
|
||||||
textAlign: TextAlign.start,
|
textAlign: TextAlign.start,
|
||||||
color: isHover || isActive ? leftBarTheme.activeItemColor : leftBarTheme.onBackground,
|
color: isHover || isActive
|
||||||
|
? leftBarTheme.activeItemColor
|
||||||
|
: leftBarTheme.onBackground,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
@ -386,7 +428,9 @@ class _MenuItemState extends State<MenuItem> with UIMixin {
|
|||||||
},
|
},
|
||||||
child: MyContainer.transparent(
|
child: MyContainer.transparent(
|
||||||
margin: MySpacing.fromLTRB(4, 0, 8, 4),
|
margin: MySpacing.fromLTRB(4, 0, 8, 4),
|
||||||
color: isActive || isHover ? leftBarTheme.activeItemBackground : Colors.transparent,
|
color: isActive || isHover
|
||||||
|
? leftBarTheme.activeItemBackground
|
||||||
|
: Colors.transparent,
|
||||||
width: MediaQuery.of(context).size.width,
|
width: MediaQuery.of(context).size.width,
|
||||||
padding: MySpacing.xy(18, 7),
|
padding: MySpacing.xy(18, 7),
|
||||||
child: MyText.bodySmall(
|
child: MyText.bodySmall(
|
||||||
@ -395,7 +439,9 @@ class _MenuItemState extends State<MenuItem> with UIMixin {
|
|||||||
maxLines: 1,
|
maxLines: 1,
|
||||||
textAlign: TextAlign.left,
|
textAlign: TextAlign.left,
|
||||||
fontSize: 12.5,
|
fontSize: 12.5,
|
||||||
color: isActive || isHover ? leftBarTheme.activeItemColor : leftBarTheme.onBackground,
|
color: isActive || isHover
|
||||||
|
? leftBarTheme.activeItemColor
|
||||||
|
: leftBarTheme.onBackground,
|
||||||
fontWeight: isActive || isHover ? 600 : 500,
|
fontWeight: isActive || isHover ? 600 : 500,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@ -410,7 +456,12 @@ class NavigationItem extends StatefulWidget {
|
|||||||
final bool isCondensed;
|
final bool isCondensed;
|
||||||
final String? route;
|
final String? route;
|
||||||
|
|
||||||
const NavigationItem({super.key, this.iconData, required this.title, this.isCondensed = false, this.route});
|
const NavigationItem(
|
||||||
|
{super.key,
|
||||||
|
this.iconData,
|
||||||
|
required this.title,
|
||||||
|
this.isCondensed = false,
|
||||||
|
this.route});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
_NavigationItemState createState() => _NavigationItemState();
|
_NavigationItemState createState() => _NavigationItemState();
|
||||||
@ -442,7 +493,9 @@ class _NavigationItemState extends State<NavigationItem> with UIMixin {
|
|||||||
},
|
},
|
||||||
child: MyContainer.transparent(
|
child: MyContainer.transparent(
|
||||||
margin: MySpacing.fromLTRB(16, 0, 16, 8),
|
margin: MySpacing.fromLTRB(16, 0, 16, 8),
|
||||||
color: isActive || isHover ? leftBarTheme.activeItemBackground : Colors.transparent,
|
color: isActive || isHover
|
||||||
|
? leftBarTheme.activeItemBackground
|
||||||
|
: Colors.transparent,
|
||||||
padding: MySpacing.xy(8, 8),
|
padding: MySpacing.xy(8, 8),
|
||||||
child: Row(
|
child: Row(
|
||||||
crossAxisAlignment: CrossAxisAlignment.center,
|
crossAxisAlignment: CrossAxisAlignment.center,
|
||||||
@ -451,7 +504,9 @@ class _NavigationItemState extends State<NavigationItem> with UIMixin {
|
|||||||
Center(
|
Center(
|
||||||
child: Icon(
|
child: Icon(
|
||||||
widget.iconData,
|
widget.iconData,
|
||||||
color: (isHover || isActive) ? leftBarTheme.activeItemColor : leftBarTheme.onBackground,
|
color: (isHover || isActive)
|
||||||
|
? leftBarTheme.activeItemColor
|
||||||
|
: leftBarTheme.onBackground,
|
||||||
size: 20,
|
size: 20,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@ -467,7 +522,9 @@ class _NavigationItemState extends State<NavigationItem> with UIMixin {
|
|||||||
widget.title,
|
widget.title,
|
||||||
overflow: TextOverflow.clip,
|
overflow: TextOverflow.clip,
|
||||||
maxLines: 1,
|
maxLines: 1,
|
||||||
color: isActive || isHover ? leftBarTheme.activeItemColor : leftBarTheme.onBackground,
|
color: isActive || isHover
|
||||||
|
? leftBarTheme.activeItemColor
|
||||||
|
: leftBarTheme.onBackground,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
],
|
],
|
||||||
|
Loading…
x
Reference in New Issue
Block a user