From 8fb32c7c8e4178d119524b069bc386c402aa5924 Mon Sep 17 00:00:00 2001 From: Vaibhav Surve Date: Wed, 3 Dec 2025 17:08:25 +0530 Subject: [PATCH] Refactor dashboard screen layout and improve loading state handling - Simplified initialization of DynamicMenuController. - Added loading skeleton for employee quick action cards. - Removed daily task planning and daily progress report from card order. - Adjusted grid layout parameters for better responsiveness. - Cleaned up code formatting for improved readability. --- lib/helpers/widgets/my_custom_skeleton.dart | 2226 ++++++++++--------- lib/view/dashboard/dashboard_screen.dart | 39 +- 2 files changed, 1237 insertions(+), 1028 deletions(-) diff --git a/lib/helpers/widgets/my_custom_skeleton.dart b/lib/helpers/widgets/my_custom_skeleton.dart index 8b2db2e..13c858c 100644 --- a/lib/helpers/widgets/my_custom_skeleton.dart +++ b/lib/helpers/widgets/my_custom_skeleton.dart @@ -5,35 +5,137 @@ import 'package:on_field_work/helpers/utils/my_shadow.dart'; class SkeletonLoaders { static Widget buildLoadingSkeleton() { - return SizedBox( - height: 360, - child: Column( - children: List.generate(5, (index) { - return Padding( - padding: const EdgeInsets.symmetric(vertical: 6), - child: SingleChildScrollView( - scrollDirection: Axis.horizontal, - child: Row( - children: List.generate(6, (i) { - return Container( - margin: const EdgeInsets.symmetric(horizontal: 4), - width: 48, - height: 16, - decoration: BoxDecoration( - color: Colors.grey.shade300, - borderRadius: BorderRadius.circular(6), - ), - ); - }), + return ShimmerEffect( + child: SizedBox( + height: 360, + child: Column( + children: List.generate(5, (index) { + return Padding( + padding: const EdgeInsets.symmetric(vertical: 6), + child: SingleChildScrollView( + scrollDirection: Axis.horizontal, + child: Row( + children: List.generate(6, (i) { + return Container( + margin: const EdgeInsets.symmetric(horizontal: 4), + width: 48, + height: 16, + decoration: BoxDecoration( + color: Colors.grey.shade300, + borderRadius: BorderRadius.circular(6), + ), + ); + }), + ), ), - ), - ); - }), + ); + }), + ), + ), + ); + } + + static Widget attendanceQuickCardSkeleton() { + return Container( + padding: const EdgeInsets.all(16), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(5), + gradient: LinearGradient( + colors: [ + Colors.grey.shade300.withOpacity(0.3), + Colors.grey.shade300.withOpacity(0.6), + ], + begin: Alignment.topLeft, + end: Alignment.bottomRight, + ), + ), + child: ShimmerEffect( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + // Row with avatar and texts + Row( + children: [ + // Avatar + Container( + width: 30, + height: 30, + decoration: BoxDecoration( + color: Colors.grey.shade400, + shape: BoxShape.circle, + ), + ), + MySpacing.width(10), + // Name + designation + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Container( + height: 12, + width: 100, + color: Colors.grey.shade400, + ), + MySpacing.height(6), + Container( + height: 10, + width: 70, + color: Colors.grey.shade400, + ), + ], + ), + ), + // Status + Container( + height: 12, + width: 60, + color: Colors.grey.shade400, + ), + ], + ), + const SizedBox(height: 12), + // Description + Container( + height: 10, + width: double.infinity, + color: Colors.grey.shade400, + ), + MySpacing.height(6), + Container( + height: 10, + width: double.infinity, + color: Colors.grey.shade400, + ), + const SizedBox(height: 12), + // Action buttons + Row( + mainAxisAlignment: MainAxisAlignment.end, + children: [ + Container( + height: 28, + width: 80, + decoration: BoxDecoration( + color: Colors.grey.shade400, + borderRadius: BorderRadius.circular(4), + ), + ), + MySpacing.width(8), + Container( + height: 28, + width: 28, + decoration: BoxDecoration( + color: Colors.grey.shade400, + shape: BoxShape.circle, + ), + ), + ], + ), + ], + ), ), ); } -// Inside SkeletonLoaders class static Widget dashboardCardsSkeleton({double? maxWidth}) { return LayoutBuilder(builder: (context, constraints) { double width = maxWidth ?? constraints.maxWidth; @@ -50,24 +152,26 @@ class SkeletonLoaders { paddingAll: 4, borderRadiusAll: 5, border: Border.all(color: Colors.grey.withOpacity(0.15)), - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Container( - width: 16, - height: 16, - decoration: BoxDecoration( - color: Colors.grey.shade300, - borderRadius: BorderRadius.circular(4), + child: ShimmerEffect( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Container( + width: 16, + height: 16, + decoration: BoxDecoration( + color: Colors.grey.shade300, + borderRadius: BorderRadius.circular(4), + ), ), - ), - MySpacing.height(4), - Container( - width: cardWidth * 0.5, - height: 10, - color: Colors.grey.shade300, - ), - ], + MySpacing.height(4), + Container( + width: cardWidth * 0.5, + height: 10, + color: Colors.grey.shade300, + ), + ], + ), ), ); }), @@ -75,7 +179,6 @@ class SkeletonLoaders { }); } -// Inside SkeletonLoaders class static Widget paymentRequestListSkeletonLoader() { return ListView.separated( padding: const EdgeInsets.fromLTRB(12, 12, 12, 80), @@ -83,92 +186,93 @@ class SkeletonLoaders { separatorBuilder: (_, __) => Divider(color: Colors.grey.shade300, height: 20), itemBuilder: (context, index) { - return Container( - padding: const EdgeInsets.symmetric(vertical: 8), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - // Category name placeholder - Container( - height: 14, - width: 120, - decoration: BoxDecoration( - color: Colors.grey.shade300, - borderRadius: BorderRadius.circular(4), - ), - ), - const SizedBox(height: 6), - - // Payee placeholder - Row( - children: [ - Container( - height: 12, - width: 50, - decoration: BoxDecoration( - color: Colors.grey.shade200, - borderRadius: BorderRadius.circular(4), - ), + return ShimmerEffect( + child: Container( + padding: const EdgeInsets.symmetric(vertical: 8), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + // Category name placeholder + Container( + height: 14, + width: 120, + decoration: BoxDecoration( + color: Colors.grey.shade300, + borderRadius: BorderRadius.circular(4), ), - const SizedBox(width: 8), - Expanded( - child: Container( + ), + const SizedBox(height: 6), + + // Payee placeholder + Row( + children: [ + Container( height: 12, + width: 50, decoration: BoxDecoration( - color: Colors.grey.shade300, + color: Colors.grey.shade200, borderRadius: BorderRadius.circular(4), ), ), - ), - ], - ), - const SizedBox(height: 6), - - // Due date and status placeholders - Row( - children: [ - // Due date label + value - Row( - children: [ - Container( + const SizedBox(width: 8), + Expanded( + child: Container( height: 12, - width: 50, - decoration: BoxDecoration( - color: Colors.grey.shade200, - borderRadius: BorderRadius.circular(4), - ), - ), - const SizedBox(width: 6), - Container( - height: 12, - width: 80, decoration: BoxDecoration( color: Colors.grey.shade300, borderRadius: BorderRadius.circular(4), ), ), - ], - ), - const Spacer(), - // Status chip placeholder - Container( - height: 20, - width: 60, - decoration: BoxDecoration( - color: Colors.grey.shade300, - borderRadius: BorderRadius.circular(6), ), - ), - ], - ), - ], + ], + ), + const SizedBox(height: 6), + + // Due date and status placeholders + Row( + children: [ + // Due date label + value + Row( + children: [ + Container( + height: 12, + width: 50, + decoration: BoxDecoration( + color: Colors.grey.shade200, + borderRadius: BorderRadius.circular(4), + ), + ), + const SizedBox(width: 6), + Container( + height: 12, + width: 80, + decoration: BoxDecoration( + color: Colors.grey.shade300, + borderRadius: BorderRadius.circular(4), + ), + ), + ], + ), + const Spacer(), + // Status chip placeholder + Container( + height: 20, + width: 60, + decoration: BoxDecoration( + color: Colors.grey.shade300, + borderRadius: BorderRadius.circular(6), + ), + ), + ], + ), + ], + ), ), ); }, ); } -// Add this inside SkeletonLoaders class static Widget paymentRequestDetailSkeletonLoader() { return SingleChildScrollView( padding: const EdgeInsets.fromLTRB(12, 12, 12, 30), @@ -179,119 +283,122 @@ class SkeletonLoaders { paddingAll: 16, borderRadiusAll: 8, shadow: MyShadow(elevation: 3), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - // Header (Created At + Status) - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Container( - width: 140, - height: 16, - color: Colors.grey.shade300, - ), - Container( - width: 80, - height: 20, - decoration: BoxDecoration( + child: ShimmerEffect( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + // Header (Created At + Status) + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Container( + width: 140, + height: 16, color: Colors.grey.shade300, - borderRadius: BorderRadius.circular(5), ), - ), - ], - ), - MySpacing.height(24), + Container( + width: 80, + height: 20, + decoration: BoxDecoration( + color: Colors.grey.shade300, + borderRadius: BorderRadius.circular(5), + ), + ), + ], + ), + MySpacing.height(24), - // Parties Section - ...List.generate( - 4, - (index) => Padding( - padding: const EdgeInsets.symmetric(vertical: 8), - child: Container( - height: 14, - width: double.infinity, - color: Colors.grey.shade300, - ), - )), - MySpacing.height(24), - - // Details Table - ...List.generate( - 6, - (index) => Padding( - padding: const EdgeInsets.symmetric(vertical: 8), - child: Container( - height: 14, - width: double.infinity, - color: Colors.grey.shade300, - ), - )), - MySpacing.height(24), - - // Documents Section - Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: List.generate( - 3, + // Parties Section + ...List.generate( + 4, (index) => Padding( padding: const EdgeInsets.symmetric(vertical: 8), child: Container( - height: 40, - decoration: BoxDecoration( - color: Colors.grey.shade300, - borderRadius: BorderRadius.circular(8), - ), + height: 14, + width: double.infinity, + color: Colors.grey.shade300, ), )), - ), - MySpacing.height(24), + MySpacing.height(24), - // Logs / Timeline - Column( - children: List.generate( - 3, - (index) => Row( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Container( - width: 16, - height: 16, + // Details Table + ...List.generate( + 6, + (index) => Padding( + padding: const EdgeInsets.symmetric(vertical: 8), + child: Container( + height: 14, + width: double.infinity, + color: Colors.grey.shade300, + ), + )), + MySpacing.height(24), + + // Documents Section + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: List.generate( + 3, + (index) => Padding( + padding: const EdgeInsets.symmetric(vertical: 8), + child: Container( + height: 40, decoration: BoxDecoration( color: Colors.grey.shade300, - shape: BoxShape.circle, + borderRadius: BorderRadius.circular(8), ), ), - MySpacing.width(12), - Expanded( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Container( - height: 14, - width: 120, - color: Colors.grey.shade300, - ), - MySpacing.height(6), - Container( - height: 12, - width: double.infinity, - color: Colors.grey.shade300, - ), - MySpacing.height(6), - Container( - height: 12, - width: 80, - color: Colors.grey.shade300, - ), - MySpacing.height(16), - ], + )), + ), + MySpacing.height(24), + + // Logs / Timeline + Column( + children: List.generate( + 3, + (index) => Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Container( + width: 16, + height: 16, + decoration: BoxDecoration( + color: Colors.grey.shade300, + shape: BoxShape.circle, + ), ), - ), - ], - )), - ), - ], + MySpacing.width(12), + Expanded( + child: Column( + crossAxisAlignment: + CrossAxisAlignment.start, + children: [ + Container( + height: 14, + width: 120, + color: Colors.grey.shade300, + ), + MySpacing.height(6), + Container( + height: 12, + width: double.infinity, + color: Colors.grey.shade300, + ), + MySpacing.height(6), + Container( + height: 12, + width: 80, + color: Colors.grey.shade300, + ), + MySpacing.height(16), + ], + ), + ), + ], + )), + ), + ], + ), ), ), ), @@ -299,7 +406,6 @@ class SkeletonLoaders { ); } -// Employee Detail Skeleton Loader static Widget employeeDetailSkeletonLoader() { return SingleChildScrollView( padding: const EdgeInsets.fromLTRB(12, 20, 12, 80), @@ -312,131 +418,135 @@ class SkeletonLoaders { paddingAll: 16, margin: MySpacing.bottom(16), shadow: MyShadow(elevation: 2), - child: Row( - children: [ - // Avatar - Container( - width: 45, - height: 45, - decoration: BoxDecoration( + child: ShimmerEffect( + child: Row( + children: [ + // Avatar + Container( + width: 45, + height: 45, + decoration: BoxDecoration( + color: Colors.grey.shade300, + shape: BoxShape.circle, + ), + ), + MySpacing.width(16), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Container( + height: 14, + width: 120, + color: Colors.grey.shade300, + ), + MySpacing.height(6), + Container( + height: 12, + width: 80, + color: Colors.grey.shade300, + ), + ], + ), + ), + Container( + width: 24, + height: 24, color: Colors.grey.shade300, - shape: BoxShape.circle, ), - ), - MySpacing.width(16), - Expanded( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Container( - height: 14, - width: 120, - color: Colors.grey.shade300, - ), - MySpacing.height(6), - Container( - height: 12, - width: 80, - color: Colors.grey.shade300, - ), - ], - ), - ), - Container( - width: 24, - height: 24, - color: Colors.grey.shade300, - ), - ], + ], + ), ), ), // Sections skeleton ...List.generate( - 4, - (_) => Column( - children: [ - MyCard( - borderRadiusAll: 8, - paddingAll: 16, - margin: MySpacing.bottom(16), - shadow: MyShadow(elevation: 2), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, + 4, + (_) => Column( + children: [ + MyCard( + borderRadiusAll: 8, + paddingAll: 16, + margin: MySpacing.bottom(16), + shadow: MyShadow(elevation: 2), + child: ShimmerEffect( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + // Section header + Row( children: [ - // Section header - Row( - children: [ - Container( - width: 20, - height: 20, - color: Colors.grey.shade300, - ), - MySpacing.width(8), - Container( - width: 120, - height: 14, - color: Colors.grey.shade300, - ), - ], + Container( + width: 20, + height: 20, + color: Colors.grey.shade300, + ), + MySpacing.width(8), + Container( + width: 120, + height: 14, + color: Colors.grey.shade300, ), - MySpacing.height(8), - const Divider(color: Colors.grey), - - // 2 rows placeholder - ...List.generate( - 2, - (_) => Padding( - padding: const EdgeInsets.symmetric( - vertical: 12), - child: Row( - crossAxisAlignment: - CrossAxisAlignment.start, - children: [ - Container( - width: 20, - height: 20, - color: Colors.grey.shade300, - ), - MySpacing.width(16), - Expanded( - child: Column( - crossAxisAlignment: - CrossAxisAlignment.start, - children: [ - Container( - height: 12, - width: 100, - color: Colors.grey.shade300, - ), - MySpacing.height(4), - Container( - height: 12, - width: 150, - color: Colors.grey.shade300, - ), - ], - ), - ), - Container( - width: 20, - height: 20, - color: Colors.grey.shade300, - ), - ], - ), - )), ], ), - ), - ], - )), + MySpacing.height(8), + const Divider(color: Colors.grey), + + // 2 rows placeholder + ...List.generate( + 2, + (_) => Padding( + padding: + const EdgeInsets.symmetric(vertical: 12), + child: Row( + crossAxisAlignment: + CrossAxisAlignment.start, + children: [ + Container( + width: 20, + height: 20, + color: Colors.grey.shade300, + ), + MySpacing.width(16), + Expanded( + child: Column( + crossAxisAlignment: + CrossAxisAlignment.start, + children: [ + Container( + height: 12, + width: 100, + color: Colors.grey.shade300, + ), + MySpacing.height(4), + Container( + height: 12, + width: 150, + color: Colors.grey.shade300, + ), + ], + ), + ), + Container( + width: 20, + height: 20, + color: Colors.grey.shade300, + ), + ], + ), + )), + ], + ), + ), + ), + ], + ), + ), ], ), ); } -// Daily Progress Planning - Infra (Expanded) Skeleton Loader static Widget dailyProgressPlanningInfraSkeleton() { return Column( crossAxisAlignment: CrossAxisAlignment.start, @@ -446,87 +556,89 @@ class SkeletonLoaders { paddingAll: 5, margin: MySpacing.bottom(10), shadow: MyShadow(elevation: 1.5), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - // Floor header placeholder - Container( - height: 14, - width: 160, - decoration: BoxDecoration( - color: Colors.grey.shade300, - borderRadius: BorderRadius.circular(6), + child: ShimmerEffect( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + // Floor header placeholder + Container( + height: 14, + width: 160, + decoration: BoxDecoration( + color: Colors.grey.shade300, + borderRadius: BorderRadius.circular(6), + ), ), - ), - MySpacing.height(10), + MySpacing.height(10), - // Divider - Divider(color: Colors.grey.withOpacity(0.3)), + // Divider + Divider(color: Colors.grey.withOpacity(0.3)), - // Work areas skeleton - Column( - children: List.generate(2, (areaIndex) { - return Padding( - padding: const EdgeInsets.only(top: 8, bottom: 6), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - // Area title - Container( - height: 12, - width: 120, - decoration: BoxDecoration( - color: Colors.grey.shade300, - borderRadius: BorderRadius.circular(4), + // Work areas skeleton + Column( + children: List.generate(2, (areaIndex) { + return Padding( + padding: const EdgeInsets.only(top: 8, bottom: 6), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + // Area title + Container( + height: 12, + width: 120, + decoration: BoxDecoration( + color: Colors.grey.shade300, + borderRadius: BorderRadius.circular(4), + ), ), - ), - MySpacing.height(8), + MySpacing.height(8), - // Work items skeleton rows - Column( - children: List.generate(2, (itemIndex) { - return Padding( - padding: const EdgeInsets.only(top: 4), - child: Row( - children: [ - // Bullet / icon - Container( - width: 8, - height: 8, - decoration: BoxDecoration( - color: Colors.grey.shade300, - shape: BoxShape.circle, - ), - ), - MySpacing.width(8), - // Item text placeholder - Expanded( - child: Container( - height: 10, + // Work items skeleton rows + Column( + children: List.generate(2, (itemIndex) { + return Padding( + padding: const EdgeInsets.only(top: 4), + child: Row( + children: [ + // Bullet / icon + Container( + width: 8, + height: 8, decoration: BoxDecoration( color: Colors.grey.shade300, - borderRadius: BorderRadius.circular(4), + shape: BoxShape.circle, ), ), - ), - ], - ), - ); - }), - ), - ], - ), - ); - }), - ), - ], + MySpacing.width(8), + // Item text placeholder + Expanded( + child: Container( + height: 10, + decoration: BoxDecoration( + color: Colors.grey.shade300, + borderRadius: + BorderRadius.circular(4), + ), + ), + ), + ], + ), + ); + }), + ), + ], + ), + ); + }), + ), + ], + ), ), ); }), ); } - // Chart Skeleton Loader (Donut Chart) static Widget chartSkeletonLoader() { return MyCard.bordered( paddingAll: 16, @@ -535,23 +647,23 @@ class SkeletonLoaders { elevation: 1.5, position: MyShadowPosition.bottom, ), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - // Chart Header Placeholder - Container( - height: 16, - width: 180, - decoration: BoxDecoration( - color: Colors.grey.shade300, - borderRadius: BorderRadius.circular(6), + child: ShimmerEffect( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + // Chart Header Placeholder + Container( + height: 16, + width: 180, + decoration: BoxDecoration( + color: Colors.grey.shade300, + borderRadius: BorderRadius.circular(6), + ), ), - ), - const SizedBox(height: 16), + const SizedBox(height: 16), - // Donut Skeleton Placeholder - Expanded( - child: Center( + // Donut Skeleton Placeholder + Center( child: Container( width: 180, height: 180, @@ -561,43 +673,43 @@ class SkeletonLoaders { ), ), ), - ), - const SizedBox(height: 16), + const SizedBox(height: 16), - // Legend placeholders - Wrap( - spacing: 8, - runSpacing: 8, - children: List.generate(5, (index) { - return Container( - width: 100, - height: 14, - decoration: BoxDecoration( - color: Colors.grey.shade300, - borderRadius: BorderRadius.circular(6), - ), - ); - }), - ), - ], + // Legend placeholders + Wrap( + spacing: 8, + runSpacing: 8, + children: List.generate(5, (index) { + return Container( + width: 100, + height: 14, + decoration: BoxDecoration( + color: Colors.grey.shade300, + borderRadius: BorderRadius.circular(6), + ), + ); + }), + ), + ], + ), ), ); } -// Date Skeleton Loader static Widget dateSkeletonLoader() { - return Container( - height: 14, - width: 90, - decoration: BoxDecoration( - color: Colors.grey.shade300, - borderRadius: BorderRadius.circular(6), + return ShimmerEffect( + child: Container( + height: 14, + width: 90, + decoration: BoxDecoration( + color: Colors.grey.shade300, + borderRadius: BorderRadius.circular(6), + ), ), ); } -// Expense By Status Skeleton Loader static Widget expenseByStatusSkeletonLoader() { return Container( padding: const EdgeInsets.all(16), @@ -613,126 +725,127 @@ class SkeletonLoaders { ), ], ), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - // Title - Container( - height: 16, - width: 160, - decoration: BoxDecoration( - color: Colors.grey.shade300, - borderRadius: BorderRadius.circular(6), + child: ShimmerEffect( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + // Title + Container( + height: 16, + width: 160, + decoration: BoxDecoration( + color: Colors.grey.shade300, + borderRadius: BorderRadius.circular(6), + ), ), - ), - const SizedBox(height: 16), + const SizedBox(height: 16), - // 4 Status Rows - ...List.generate(4, (index) { - return Padding( - padding: const EdgeInsets.symmetric(vertical: 6), - child: Row( - children: [ - // Icon placeholder - Container( - height: 44, - width: 44, - decoration: BoxDecoration( - color: Colors.grey.shade300, - shape: BoxShape.circle, + // 4 Status Rows + ...List.generate(4, (index) { + return Padding( + padding: const EdgeInsets.symmetric(vertical: 6), + child: Row( + children: [ + // Icon placeholder + Container( + height: 44, + width: 44, + decoration: BoxDecoration( + color: Colors.grey.shade300, + shape: BoxShape.circle, + ), ), - ), - const SizedBox(width: 12), + const SizedBox(width: 12), - // Title + Amount - Expanded( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Container( - height: 12, - width: 100, - decoration: BoxDecoration( - color: Colors.grey.shade300, - borderRadius: BorderRadius.circular(4), + // Title + Amount + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Container( + height: 12, + width: 100, + decoration: BoxDecoration( + color: Colors.grey.shade300, + borderRadius: BorderRadius.circular(4), + ), ), - ), - const SizedBox(height: 6), - Container( - height: 12, - width: 60, - decoration: BoxDecoration( - color: Colors.grey.shade300, - borderRadius: BorderRadius.circular(4), + const SizedBox(height: 6), + Container( + height: 12, + width: 60, + decoration: BoxDecoration( + color: Colors.grey.shade300, + borderRadius: BorderRadius.circular(4), + ), ), - ), - ], + ], + ), ), - ), - // Count + arrow placeholder - Container( - height: 12, - width: 30, - decoration: BoxDecoration( - color: Colors.grey.shade300, - borderRadius: BorderRadius.circular(4), + // Count + arrow placeholder + Container( + height: 12, + width: 30, + decoration: BoxDecoration( + color: Colors.grey.shade300, + borderRadius: BorderRadius.circular(4), + ), ), - ), - const SizedBox(width: 6), - Icon(Icons.chevron_right, - color: Colors.grey.shade300, size: 24), - ], - ), - ); - }), - - const SizedBox(height: 16), - Divider(color: Colors.grey.shade300), - const SizedBox(height: 12), - - // Bottom Row (Project Spendings) - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Container( - height: 12, - width: 120, - decoration: BoxDecoration( - color: Colors.grey.shade300, - borderRadius: BorderRadius.circular(4), - ), - ), - const SizedBox(height: 4), - Container( - height: 10, - width: 140, - decoration: BoxDecoration( - color: Colors.grey.shade300, - borderRadius: BorderRadius.circular(4), - ), - ), - ], - ), - Container( - height: 16, - width: 80, - decoration: BoxDecoration( - color: Colors.grey.shade300, - borderRadius: BorderRadius.circular(4), + const SizedBox(width: 6), + Icon(Icons.chevron_right, + color: Colors.grey.shade300, size: 24), + ], ), - ), - ], - ), - ], + ); + }), + + const SizedBox(height: 16), + Divider(color: Colors.grey.shade300), + const SizedBox(height: 12), + + // Bottom Row (Project Spendings) + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Container( + height: 12, + width: 120, + decoration: BoxDecoration( + color: Colors.grey.shade300, + borderRadius: BorderRadius.circular(4), + ), + ), + const SizedBox(height: 4), + Container( + height: 10, + width: 140, + decoration: BoxDecoration( + color: Colors.grey.shade300, + borderRadius: BorderRadius.circular(4), + ), + ), + ], + ), + Container( + height: 16, + width: 80, + decoration: BoxDecoration( + color: Colors.grey.shade300, + borderRadius: BorderRadius.circular(4), + ), + ), + ], + ), + ], + ), ), ); } -// Document List Skeleton Loader static Widget documentSkeletonLoader() { return Column( children: List.generate(5, (index) { @@ -742,12 +855,14 @@ class SkeletonLoaders { // Date placeholder Padding( padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 6), - child: Container( - height: 12, - width: 80, - decoration: BoxDecoration( - color: Colors.grey.shade300, - borderRadius: BorderRadius.circular(6), + child: ShimmerEffect( + child: Container( + height: 12, + width: 80, + decoration: BoxDecoration( + color: Colors.grey.shade300, + borderRadius: BorderRadius.circular(6), + ), ), ), ), @@ -767,57 +882,59 @@ class SkeletonLoaders { ), ], ), - child: Row( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - // Icon Placeholder - Container( - padding: const EdgeInsets.all(10), - decoration: BoxDecoration( - color: Colors.grey.shade300, - borderRadius: BorderRadius.circular(8), + child: ShimmerEffect( + child: Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + // Icon Placeholder + Container( + padding: const EdgeInsets.all(10), + decoration: BoxDecoration( + color: Colors.grey.shade300, + borderRadius: BorderRadius.circular(8), + ), + child: const Icon(Icons.description, + color: Colors.transparent), // invisible icon ), - child: const Icon(Icons.description, - color: Colors.transparent), // invisible icon - ), - const SizedBox(width: 12), + const SizedBox(width: 12), - // Text placeholders - Expanded( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Container( - height: 12, - width: 80, - color: Colors.grey.shade300, - ), - MySpacing.height(6), - Container( - height: 14, - width: double.infinity, - color: Colors.grey.shade300, - ), - MySpacing.height(6), - Container( - height: 12, - width: 100, - color: Colors.grey.shade300, - ), - ], + // Text placeholders + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Container( + height: 12, + width: 80, + color: Colors.grey.shade300, + ), + MySpacing.height(6), + Container( + height: 14, + width: double.infinity, + color: Colors.grey.shade300, + ), + MySpacing.height(6), + Container( + height: 12, + width: 100, + color: Colors.grey.shade300, + ), + ], + ), ), - ), - // Action icon placeholder - Container( - width: 20, - height: 20, - decoration: BoxDecoration( - color: Colors.grey.shade300, - shape: BoxShape.circle, + // Action icon placeholder + Container( + width: 20, + height: 20, + decoration: BoxDecoration( + color: Colors.grey.shade300, + shape: BoxShape.circle, + ), ), - ), - ], + ], + ), ), ), ], @@ -826,7 +943,6 @@ class SkeletonLoaders { ); } -// Document Details Card Skeleton Loader static Widget documentDetailsSkeletonLoader() { return SingleChildScrollView( padding: const EdgeInsets.all(16), @@ -848,85 +964,87 @@ class SkeletonLoaders { ), ], ), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - // Header - Row( - children: [ - Container( - width: 56, - height: 56, - decoration: BoxDecoration( - color: Colors.grey.shade300, - shape: BoxShape.circle, + child: ShimmerEffect( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + // Header + Row( + children: [ + Container( + width: 56, + height: 56, + decoration: BoxDecoration( + color: Colors.grey.shade300, + shape: BoxShape.circle, + ), ), - ), - const SizedBox(width: 16), - Expanded( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Container( - height: 16, - width: 180, - color: Colors.grey.shade300, - ), - const SizedBox(height: 8), - Container( - height: 12, - width: 120, - color: Colors.grey.shade300, - ), - ], - ), - ), - ], - ), - const SizedBox(height: 12), - - // Tags placeholder - Wrap( - spacing: 6, - runSpacing: 6, - children: List.generate(3, (index) { - return Container( - height: 20, - width: 60, - decoration: BoxDecoration( - color: Colors.grey.shade300, - borderRadius: BorderRadius.circular(12), - ), - ); - }), - ), - const SizedBox(height: 16), - - // Info rows placeholders - Column( - children: List.generate(10, (index) { - return Padding( - padding: const EdgeInsets.symmetric(vertical: 6), - child: Row( - children: [ - Container( - height: 12, - width: 120, - color: Colors.grey.shade300, - ), - const SizedBox(width: 12), - Expanded( - child: Container( - height: 12, + const SizedBox(width: 16), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Container( + height: 16, + width: 180, color: Colors.grey.shade300, ), - ), - ], + const SizedBox(height: 8), + Container( + height: 12, + width: 120, + color: Colors.grey.shade300, + ), + ], + ), ), - ); - }), - ), - ], + ], + ), + const SizedBox(height: 12), + + // Tags placeholder + Wrap( + spacing: 6, + runSpacing: 6, + children: List.generate(3, (index) { + return Container( + height: 20, + width: 60, + decoration: BoxDecoration( + color: Colors.grey.shade300, + borderRadius: BorderRadius.circular(12), + ), + ); + }), + ), + const SizedBox(height: 16), + + // Info rows placeholders + Column( + children: List.generate(10, (index) { + return Padding( + padding: const EdgeInsets.symmetric(vertical: 6), + child: Row( + children: [ + Container( + height: 12, + width: 120, + color: Colors.grey.shade300, + ), + const SizedBox(width: 12), + Expanded( + child: Container( + height: 12, + color: Colors.grey.shade300, + ), + ), + ], + ), + ); + }), + ), + ], + ), ), ), @@ -935,52 +1053,54 @@ class SkeletonLoaders { // Versions section skeleton Container( margin: const EdgeInsets.symmetric(horizontal: 12), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: List.generate(3, (index) { - return Padding( - padding: const EdgeInsets.symmetric(vertical: 6), - child: Row( - children: [ - Container( - width: 40, - height: 40, - decoration: BoxDecoration( - color: Colors.grey.shade300, - borderRadius: BorderRadius.circular(8), + child: ShimmerEffect( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: List.generate(3, (index) { + return Padding( + padding: const EdgeInsets.symmetric(vertical: 6), + child: Row( + children: [ + Container( + width: 40, + height: 40, + decoration: BoxDecoration( + color: Colors.grey.shade300, + borderRadius: BorderRadius.circular(8), + ), ), - ), - const SizedBox(width: 12), - Expanded( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Container( - height: 12, - width: 180, - color: Colors.grey.shade300, - ), - const SizedBox(height: 6), - Container( - height: 10, - width: 120, - color: Colors.grey.shade300, - ), - ], + const SizedBox(width: 12), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Container( + height: 12, + width: 180, + color: Colors.grey.shade300, + ), + const SizedBox(height: 6), + Container( + height: 10, + width: 120, + color: Colors.grey.shade300, + ), + ], + ), ), - ), - Container( - width: 24, - height: 24, - decoration: BoxDecoration( - color: Colors.grey.shade300, - shape: BoxShape.circle, + Container( + width: 24, + height: 24, + decoration: BoxDecoration( + color: Colors.grey.shade300, + shape: BoxShape.circle, + ), ), - ), - ], - ), - ); - }), + ], + ), + ); + }), + ), ), ), ], @@ -988,7 +1108,6 @@ class SkeletonLoaders { ); } - // Employee List - Card Style static Widget employeeListSkeletonLoader() { return Column( children: List.generate(4, (index) { @@ -997,172 +1116,176 @@ class SkeletonLoaders { paddingAll: 10, margin: MySpacing.bottom(12), shadow: MyShadow(elevation: 3), - child: Row( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - // Avatar - Container( - width: 41, - height: 41, - decoration: BoxDecoration( - color: Colors.grey.shade300, - shape: BoxShape.circle, + child: ShimmerEffect( + child: Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + // Avatar + Container( + width: 41, + height: 41, + decoration: BoxDecoration( + color: Colors.grey.shade300, + shape: BoxShape.circle, + ), ), - ), - MySpacing.width(16), - Expanded( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Row( - children: [ - Container( - height: 14, - width: 100, - color: Colors.grey.shade300), - MySpacing.width(8), - Container( - height: 12, width: 60, color: Colors.grey.shade300), - ], - ), - MySpacing.height(8), - Row( - children: [ - Icon(Icons.email, - size: 16, color: Colors.grey.shade300), - MySpacing.width(4), - Container( - height: 10, - width: 140, - color: Colors.grey.shade300), - ], - ), - MySpacing.height(8), - Row( - children: [ - Icon(Icons.phone, - size: 16, color: Colors.grey.shade300), - MySpacing.width(4), - Container( - height: 10, - width: 100, - color: Colors.grey.shade300), - ], - ), - ], + MySpacing.width(16), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + Container( + height: 14, + width: 100, + color: Colors.grey.shade300), + MySpacing.width(8), + Container( + height: 12, + width: 60, + color: Colors.grey.shade300), + ], + ), + MySpacing.height(8), + Row( + children: [ + Icon(Icons.email, + size: 16, color: Colors.grey.shade300), + MySpacing.width(4), + Container( + height: 10, + width: 140, + color: Colors.grey.shade300), + ], + ), + MySpacing.height(8), + Row( + children: [ + Icon(Icons.phone, + size: 16, color: Colors.grey.shade300), + MySpacing.width(4), + Container( + height: 10, + width: 100, + color: Colors.grey.shade300), + ], + ), + ], + ), ), - ), - ], + ], + ), ), ); }), ); } - // Employee List - Compact Collapsed Style static Widget employeeListCollapsedSkeletonLoader() { return MyCard.bordered( borderRadiusAll: 4, paddingAll: 8, - child: Column( - children: List.generate(4, (index) { - return Column( - children: [ - Padding( - padding: const EdgeInsets.only(bottom: 8), - child: Row( - children: [ - // Avatar - Container( - width: 31, - height: 31, - decoration: BoxDecoration( - color: Colors.grey.shade300, - shape: BoxShape.circle, + child: ShimmerEffect( + child: Column( + children: List.generate(4, (index) { + return Column( + children: [ + Padding( + padding: const EdgeInsets.only(bottom: 8), + child: Row( + children: [ + // Avatar + Container( + width: 31, + height: 31, + decoration: BoxDecoration( + color: Colors.grey.shade300, + shape: BoxShape.circle, + ), ), - ), - MySpacing.width(16), - // Name, Designation & Buttons - Expanded( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Container( - height: 12, - width: 100, - color: Colors.grey.shade300), - MySpacing.height(8), - Container( - height: 10, - width: 80, - color: Colors.grey.shade300), - MySpacing.height(12), - Row( - mainAxisAlignment: MainAxisAlignment.end, - children: [ - Container( - height: 28, - width: 60, - color: Colors.grey.shade300), - MySpacing.width(8), - Container( - height: 28, - width: 60, - color: Colors.grey.shade300), - ], - ), - ], + MySpacing.width(16), + // Name, Designation & Buttons + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Container( + height: 12, + width: 100, + color: Colors.grey.shade300), + MySpacing.height(8), + Container( + height: 10, + width: 80, + color: Colors.grey.shade300), + MySpacing.height(12), + Row( + mainAxisAlignment: MainAxisAlignment.end, + children: [ + Container( + height: 28, + width: 60, + color: Colors.grey.shade300), + MySpacing.width(8), + Container( + height: 28, + width: 60, + color: Colors.grey.shade300), + ], + ), + ], + ), ), - ), - ], + ], + ), ), - ), - if (index != 3) - Divider( - color: Colors.grey.withOpacity(0.3), - thickness: 1, - height: 1, - ), - ], - ); - }), + if (index != 3) + Divider( + color: Colors.grey.withOpacity(0.3), + thickness: 1, + height: 1, + ), + ], + ); + }), + ), ), ); } - // Daily Progress Report Header Loader static Widget dailyProgressReportSkeletonLoader() { return MyCard.bordered( borderRadiusAll: 4, border: Border.all(color: Colors.grey.withOpacity(0.2)), shadow: MyShadow(elevation: 1, position: MyShadowPosition.bottom), paddingAll: 8, - child: Column( - children: List.generate(3, (index) { - return Column( - children: [ - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Container( - height: 14, width: 120, color: Colors.grey.shade300), - Icon(Icons.add_circle, color: Colors.grey.shade300), + child: ShimmerEffect( + child: Column( + children: List.generate(3, (index) { + return Column( + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Container( + height: 14, width: 120, color: Colors.grey.shade300), + Icon(Icons.add_circle, color: Colors.grey.shade300), + ], + ), + if (index != 2) ...[ + MySpacing.height(12), + Divider(color: Colors.grey.withOpacity(0.3), thickness: 1), + MySpacing.height(12), ], - ), - if (index != 2) ...[ - MySpacing.height(12), - Divider(color: Colors.grey.withOpacity(0.3), thickness: 1), - MySpacing.height(12), ], - ], - ); - }), + ); + }), + ), ), ); } - // Daily Progress Planning (Collapsed View) - static Widget dailyProgressPlanningSkeletonCollapsedOnly() { return Column( crossAxisAlignment: CrossAxisAlignment.start, @@ -1172,33 +1295,35 @@ class SkeletonLoaders { paddingAll: 16, margin: MySpacing.bottom(12), shadow: MyShadow(elevation: 3), - child: Row( - children: [ - // Icon placeholder - Container( - width: 40, - height: 40, - decoration: BoxDecoration( - color: Colors.grey.shade300, - shape: BoxShape.circle, + child: ShimmerEffect( + child: Row( + children: [ + // Icon placeholder + Container( + width: 40, + height: 40, + decoration: BoxDecoration( + color: Colors.grey.shade300, + shape: BoxShape.circle, + ), ), - ), - MySpacing.width(12), - // Text line - Expanded( - child: Container(height: 16, color: Colors.grey.shade300), - ), - MySpacing.width(12), - // Expand button placeholder - Container( - width: 28, - height: 28, - decoration: BoxDecoration( - shape: BoxShape.circle, - color: Colors.grey.shade300, + MySpacing.width(12), + // Text line + Expanded( + child: Container(height: 16, color: Colors.grey.shade300), ), - ), - ], + MySpacing.width(12), + // Expand button placeholder + Container( + width: 28, + height: 28, + decoration: BoxDecoration( + shape: BoxShape.circle, + color: Colors.grey.shade300, + ), + ), + ], + ), ), ); }), @@ -1208,59 +1333,61 @@ class SkeletonLoaders { static Widget expenseListSkeletonLoader() { return ListView.separated( padding: const EdgeInsets.fromLTRB(12, 12, 12, 80), - itemCount: 6, // Show 6 skeleton items + itemCount: 6, separatorBuilder: (_, __) => Divider(color: Colors.grey.shade300, height: 20), itemBuilder: (context, index) { - return Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - // Title and Amount - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Container( - height: 14, - width: 120, - decoration: BoxDecoration( - color: Colors.grey.shade300, - borderRadius: BorderRadius.circular(6), + return ShimmerEffect( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + // Title and Amount + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Container( + height: 14, + width: 120, + decoration: BoxDecoration( + color: Colors.grey.shade300, + borderRadius: BorderRadius.circular(6), + ), ), - ), - Container( - height: 14, - width: 80, - decoration: BoxDecoration( - color: Colors.grey.shade300, - borderRadius: BorderRadius.circular(6), + Container( + height: 14, + width: 80, + decoration: BoxDecoration( + color: Colors.grey.shade300, + borderRadius: BorderRadius.circular(6), + ), ), - ), - ], - ), - const SizedBox(height: 6), - // Date and Status - Row( - children: [ - Container( - height: 12, - width: 100, - decoration: BoxDecoration( - color: Colors.grey.shade300, - borderRadius: BorderRadius.circular(6), + ], + ), + const SizedBox(height: 6), + // Date and Status + Row( + children: [ + Container( + height: 12, + width: 100, + decoration: BoxDecoration( + color: Colors.grey.shade300, + borderRadius: BorderRadius.circular(6), + ), ), - ), - const Spacer(), - Container( - height: 12, - width: 50, - decoration: BoxDecoration( - color: Colors.grey.shade300, - borderRadius: BorderRadius.circular(6), + const Spacer(), + Container( + height: 12, + width: 50, + decoration: BoxDecoration( + color: Colors.grey.shade300, + borderRadius: BorderRadius.circular(6), + ), ), - ), - ], - ), - ], + ], + ), + ], + ), ); }, ); @@ -1275,72 +1402,76 @@ class SkeletonLoaders { elevation: 1.5, position: MyShadowPosition.bottom, ), - child: Row( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - // Avatar - Container( - height: 35, - width: 35, - decoration: BoxDecoration( - color: Colors.grey.shade300, - shape: BoxShape.circle, + child: ShimmerEffect( + child: Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + // Avatar + Container( + height: 35, + width: 35, + decoration: BoxDecoration( + color: Colors.grey.shade300, + shape: BoxShape.circle, + ), ), - ), - MySpacing.width(12), + MySpacing.width(12), - // Name, org, email, phone - Expanded( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Container(height: 12, width: 120, color: Colors.grey.shade300), - MySpacing.height(6), - Container(height: 10, width: 80, color: Colors.grey.shade300), - MySpacing.height(8), + // Name, org, email, phone + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Container( + height: 12, width: 120, color: Colors.grey.shade300), + MySpacing.height(6), + Container(height: 10, width: 80, color: Colors.grey.shade300), + MySpacing.height(8), - // Email placeholder - Row( - children: [ - Icon(Icons.email_outlined, - size: 14, color: Colors.grey.shade300), - MySpacing.width(4), - Container( - height: 10, width: 140, color: Colors.grey.shade300), - ], - ), - MySpacing.height(8), + // Email placeholder + Row( + children: [ + Icon(Icons.email_outlined, + size: 14, color: Colors.grey.shade300), + MySpacing.width(4), + Container( + height: 10, width: 140, color: Colors.grey.shade300), + ], + ), + MySpacing.height(8), - // Phone placeholder - Row( - children: [ - Icon(Icons.phone_outlined, - size: 14, color: Colors.grey.shade300), - MySpacing.width(4), - Container( - height: 10, width: 100, color: Colors.grey.shade300), - MySpacing.width(8), - Container( - height: 16, - width: 16, - decoration: BoxDecoration( - color: Colors.grey.shade300, - shape: BoxShape.circle, + // Phone placeholder + Row( + children: [ + Icon(Icons.phone_outlined, + size: 14, color: Colors.grey.shade300), + MySpacing.width(4), + Container( + height: 10, width: 100, color: Colors.grey.shade300), + MySpacing.width(8), + Container( + height: 16, + width: 16, + decoration: BoxDecoration( + color: Colors.grey.shade300, + shape: BoxShape.circle, + ), ), - ), - ], - ), - MySpacing.height(8), + ], + ), + MySpacing.height(8), - // Tags placeholder - Container(height: 8, width: 80, color: Colors.grey.shade300), - ], + // Tags placeholder + Container(height: 8, width: 80, color: Colors.grey.shade300), + ], + ), ), - ), - // Arrow - Icon(Icons.arrow_forward_ios, size: 14, color: Colors.grey.shade300), - ], + // Arrow + Icon(Icons.arrow_forward_ios, + size: 14, color: Colors.grey.shade300), + ], + ), ), ); } @@ -1354,48 +1485,131 @@ class SkeletonLoaders { elevation: 1.5, position: MyShadowPosition.bottom, ), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Row( - children: [ - Container( - height: 40, - width: 40, - decoration: BoxDecoration( - color: Colors.grey.shade300, - shape: BoxShape.circle, + child: ShimmerEffect( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + Container( + height: 40, + width: 40, + decoration: BoxDecoration( + color: Colors.grey.shade300, + shape: BoxShape.circle, + ), ), - ), - MySpacing.width(12), - Expanded( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Container( - height: 12, - width: 100, - color: Colors.grey.shade300, - ), - MySpacing.height(6), - Container( - height: 10, - width: 60, - color: Colors.grey.shade300, - ), - ], + MySpacing.width(12), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Container( + height: 12, + width: 100, + color: Colors.grey.shade300, + ), + MySpacing.height(6), + Container( + height: 10, + width: 60, + color: Colors.grey.shade300, + ), + ], + ), ), - ), - ], - ), - MySpacing.height(16), - Container(height: 10, width: 150, color: Colors.grey.shade300), - MySpacing.height(8), - Container(height: 10, width: 100, color: Colors.grey.shade300), - MySpacing.height(8), - Container(height: 10, width: 120, color: Colors.grey.shade300), - ], + ], + ), + MySpacing.height(16), + Container(height: 10, width: 150, color: Colors.grey.shade300), + MySpacing.height(8), + Container(height: 10, width: 100, color: Colors.grey.shade300), + MySpacing.height(8), + Container(height: 10, width: 120, color: Colors.grey.shade300), + ], + ), ), ); } } + +/// A custom reusable Shimmer Effect widget. +/// This creates a gradient wave animation over any child widget. +class ShimmerEffect extends StatefulWidget { + final Widget child; + final Color baseColor; + final Color highlightColor; + + ShimmerEffect({ + Key? key, + required this.child, + Color? baseColor, + Color? highlightColor, + }) : baseColor = baseColor ?? Colors.grey.shade300, + highlightColor = highlightColor ?? Colors.grey.shade100, + super(key: key); + + @override + State createState() => _ShimmerEffectState(); +} + +class _ShimmerEffectState extends State + with SingleTickerProviderStateMixin { + late AnimationController _controller; + + @override + void initState() { + super.initState(); + _controller = AnimationController( + vsync: this, + duration: const Duration(seconds: 2), // Adjust speed here + )..repeat(); + } + + @override + void dispose() { + _controller.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return AnimatedBuilder( + animation: _controller, + child: widget.child, + builder: (context, child) { + return ShaderMask( + blendMode: BlendMode.srcATop, + shaderCallback: (bounds) { + final gradient = LinearGradient( + colors: [ + widget.baseColor, + widget.highlightColor, + widget.baseColor, + ], + stops: const [0.1, 0.5, 0.9], + begin: const Alignment(-1.0, -0.3), + end: const Alignment(1.0, 0.3), + transform: _SlidingGradientTransform(_controller.value), + ); + return gradient.createShader(bounds); + }, + child: child, + ); + }, + ); + } +} + +class _SlidingGradientTransform extends GradientTransform { + final double percent; + + const _SlidingGradientTransform(this.percent); + + @override + Matrix4? transform(Rect bounds, {TextDirection? textDirection}) { + // This moves the gradient across the width of the widget + return Matrix4.translationValues( + (bounds.width * 2) * percent - bounds.width, 0, 0); + } +} diff --git a/lib/view/dashboard/dashboard_screen.dart b/lib/view/dashboard/dashboard_screen.dart index 7f3a0db..91a24a1 100644 --- a/lib/view/dashboard/dashboard_screen.dart +++ b/lib/view/dashboard/dashboard_screen.dart @@ -31,8 +31,7 @@ class _DashboardScreenState extends State with UIMixin { Get.put(DashboardController(), permanent: true); final AttendanceController attendanceController = Get.put(AttendanceController()); - final DynamicMenuController menuController = - Get.put(DynamicMenuController()); + final DynamicMenuController menuController = Get.put(DynamicMenuController()); final ProjectController projectController = Get.find(); bool hasMpin = true; @@ -97,6 +96,11 @@ class _DashboardScreenState extends State with UIMixin { children: [ _sectionTitle('Quick Action'), Obx(() { + if (dashboardController.isLoadingEmployees.value) { + // Show loading skeleton + return SkeletonLoaders.attendanceQuickCardSkeleton(); + } + final employees = dashboardController.employees; final employee = employees.isNotEmpty ? employees.first : null; @@ -121,6 +125,7 @@ class _DashboardScreenState extends State with UIMixin { ); } + // Actual employee quick action card final bool isCheckedIn = employee.checkIn != null; final bool isCheckedOut = employee.checkOut != null; @@ -233,8 +238,6 @@ class _DashboardScreenState extends State with UIMixin { final List cardOrder = [ MenuItems.attendance, MenuItems.employees, - MenuItems.dailyTaskPlanning, - MenuItems.dailyProgressReport, MenuItems.directory, MenuItems.finance, MenuItems.documents, @@ -247,14 +250,10 @@ class _DashboardScreenState extends State with UIMixin { _DashboardCardMeta(LucideIcons.scan_face, contentTheme.success), MenuItems.employees: _DashboardCardMeta(LucideIcons.users, contentTheme.warning), - MenuItems.dailyTaskPlanning: - _DashboardCardMeta(LucideIcons.logs, contentTheme.info), - MenuItems.dailyProgressReport: - _DashboardCardMeta(LucideIcons.list_todo, contentTheme.info), MenuItems.directory: _DashboardCardMeta(LucideIcons.folder, contentTheme.info), MenuItems.finance: - _DashboardCardMeta(LucideIcons.wallet, contentTheme.info), + _DashboardCardMeta(LucideIcons.wallet, contentTheme.info), MenuItems.documents: _DashboardCardMeta(LucideIcons.file_text, contentTheme.info), MenuItems.serviceProjects: @@ -314,10 +313,10 @@ class _DashboardScreenState extends State with UIMixin { physics: const NeverScrollableScrollPhysics(), padding: const EdgeInsets.symmetric(vertical: 2, horizontal: 2), gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount( - crossAxisCount: 4, - crossAxisSpacing: 8, + crossAxisCount: 3, + crossAxisSpacing: 15, mainAxisSpacing: 8, - childAspectRatio: 1.15, + childAspectRatio: 1.8, ), itemCount: filtered.length, itemBuilder: (context, index) { @@ -367,9 +366,8 @@ class _DashboardScreenState extends State with UIMixin { Icon( cardMeta.icon, size: 20, - color: isEnabled - ? cardMeta.color - : Colors.grey.shade300, + color: + isEnabled ? cardMeta.color : Colors.grey.shade300, ), const SizedBox(height: 6), Padding( @@ -379,9 +377,8 @@ class _DashboardScreenState extends State with UIMixin { textAlign: TextAlign.center, style: TextStyle( fontSize: 10, - fontWeight: isEnabled - ? FontWeight.w600 - : FontWeight.w400, + fontWeight: + isEnabled ? FontWeight.w600 : FontWeight.w400, color: isEnabled ? Colors.black87 : Colors.grey.shade400, @@ -424,11 +421,9 @@ class _DashboardScreenState extends State with UIMixin { children: [ _sectionTitle('Project'), GestureDetector( - onTap: () => - projectController.isProjectSelectionExpanded.toggle(), + onTap: () => projectController.isProjectSelectionExpanded.toggle(), child: Container( - padding: - const EdgeInsets.symmetric(horizontal: 14, vertical: 14), + padding: const EdgeInsets.symmetric(horizontal: 14, vertical: 14), decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.circular(5),