feat: Implement loading skeletons in dashboard and layout screens for better UX

This commit is contained in:
Vaibhav Surve 2025-06-19 15:35:09 +05:30
parent 660bd3cdf1
commit ef6521faa2
3 changed files with 97 additions and 4 deletions

View File

@ -319,7 +319,7 @@ class ApiService {
};
final response = await _postRequest(
ApiEndpoints.commentTask,
ApiEndpoints.reportTask,
body,
customTimeout: extendedTimeout,
);

View File

@ -133,8 +133,21 @@ class _DashboardScreenState extends State<DashboardScreen> with UIMixin {
return GetBuilder<ProjectController>(
id: 'dashboard_controller',
builder: (controller) {
final bool isLoading = controller.isLoading.value;
final bool isProjectSelected = controller.selectedProject != null;
if (isLoading) {
return Wrap(
spacing: 10,
runSpacing: 10,
children: List.generate(
4,
(index) =>
_buildStatCardSkeleton(MediaQuery.of(context).size.width / 3),
),
);
}
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
@ -184,6 +197,33 @@ class _DashboardScreenState extends State<DashboardScreen> with UIMixin {
);
}
Widget _buildStatCardSkeleton(double width) {
return MyCard.bordered(
width: width,
height: 100,
paddingAll: 5,
borderRadiusAll: 10,
border: Border.all(color: Colors.grey.withOpacity(0.15)),
shadow: MyShadow(elevation: 1.5, position: MyShadowPosition.bottom),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
MyContainer.rounded(
paddingAll: 12,
color: Colors.grey.shade300,
child: const SizedBox(width: 18, height: 18),
),
MySpacing.height(8),
Container(
height: 12,
width: 60,
color: Colors.grey.shade300,
),
],
),
);
}
Widget _buildStatCard(_StatItem statItem, double width, bool isEnabled) {
return Opacity(
opacity: isEnabled ? 1.0 : 0.4,

View File

@ -68,7 +68,6 @@ class _LayoutState extends State<Layout> {
),
],
),
// Project dropdown overlay
Obx(() {
if (!projectController.isProjectSelectionExpanded.value) {
return const SizedBox.shrink();
@ -102,6 +101,12 @@ class _LayoutState extends State<Layout> {
return Padding(
padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 6),
child: Obx(() {
final isLoading = projectController.isLoading.value;
if (isLoading) {
return _buildLoadingSkeleton();
}
final isExpanded = projectController.isProjectSelectionExpanded.value;
final selectedProjectId = projectController.selectedProjectId?.value;
final selectedProject = projectController.projects.firstWhereOrNull(
@ -220,8 +225,6 @@ class _LayoutState extends State<Layout> {
],
),
),
// Expanded Project List inside card only show if projects exist
if (isExpanded && hasProjects)
Positioned(
top: 70,
@ -240,6 +243,56 @@ class _LayoutState extends State<Layout> {
);
}
Widget _buildLoadingSkeleton() {
return Card(
elevation: 4,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12),
),
margin: EdgeInsets.zero,
child: Padding(
padding: const EdgeInsets.all(10),
child: Row(
children: [
Container(
height: 50,
width: 50,
decoration: BoxDecoration(
color: Colors.grey.shade300,
borderRadius: BorderRadius.circular(8),
),
),
const SizedBox(width: 12),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Container(
height: 18,
width: 140,
color: Colors.grey.shade300,
),
const SizedBox(height: 6),
Container(
height: 14,
width: 100,
color: Colors.grey.shade200,
),
],
),
),
const SizedBox(width: 10),
Container(
height: 30,
width: 30,
color: Colors.grey.shade300,
),
],
),
),
);
}
Widget _buildProjectList(BuildContext context, bool isMobile) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,