From ef6521faa27a1a6d6e3efa09b8a798434ff52e4c Mon Sep 17 00:00:00 2001 From: Vaibhav Surve Date: Thu, 19 Jun 2025 15:35:09 +0530 Subject: [PATCH] feat: Implement loading skeletons in dashboard and layout screens for better UX --- lib/helpers/services/api_service.dart | 2 +- lib/view/dashboard/dashboard_screen.dart | 40 ++++++++++++++++ lib/view/layouts/layout.dart | 59 ++++++++++++++++++++++-- 3 files changed, 97 insertions(+), 4 deletions(-) diff --git a/lib/helpers/services/api_service.dart b/lib/helpers/services/api_service.dart index 40ef2b6..bd7f81c 100644 --- a/lib/helpers/services/api_service.dart +++ b/lib/helpers/services/api_service.dart @@ -319,7 +319,7 @@ class ApiService { }; final response = await _postRequest( - ApiEndpoints.commentTask, + ApiEndpoints.reportTask, body, customTimeout: extendedTimeout, ); diff --git a/lib/view/dashboard/dashboard_screen.dart b/lib/view/dashboard/dashboard_screen.dart index 5227333..82ebe76 100644 --- a/lib/view/dashboard/dashboard_screen.dart +++ b/lib/view/dashboard/dashboard_screen.dart @@ -133,8 +133,21 @@ class _DashboardScreenState extends State with UIMixin { return GetBuilder( 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 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, diff --git a/lib/view/layouts/layout.dart b/lib/view/layouts/layout.dart index 5d5f909..6083c23 100644 --- a/lib/view/layouts/layout.dart +++ b/lib/view/layouts/layout.dart @@ -68,7 +68,6 @@ class _LayoutState extends State { ), ], ), - // Project dropdown overlay Obx(() { if (!projectController.isProjectSelectionExpanded.value) { return const SizedBox.shrink(); @@ -102,6 +101,12 @@ class _LayoutState extends State { 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 { ], ), ), - - // Expanded Project List inside card — only show if projects exist if (isExpanded && hasProjects) Positioned( top: 70, @@ -240,6 +243,56 @@ class _LayoutState extends State { ); } + 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,