diff --git a/lib/helpers/widgets/my_custom_skeleton.dart b/lib/helpers/widgets/my_custom_skeleton.dart index a2c582d..f3f6eed 100644 --- a/lib/helpers/widgets/my_custom_skeleton.dart +++ b/lib/helpers/widgets/my_custom_skeleton.dart @@ -35,6 +35,143 @@ class SkeletonLoaders { ); } + static Widget serviceProjectListSkeletonLoader() { + // --- Start: Configuration to match live UI --- + // Live UI uses ListView.separated with: + // - padding: MySpacing.only(left: 8, right: 8, top: 4, bottom: 120) + // - separatorBuilder: MySpacing.height(12) + // - _buildProjectCard uses Card(margin: const EdgeInsets.symmetric(horizontal: 6, vertical: 4)) + + // To combine: + // Horizontal padding: 8 (ListView) + 6 (Card margin) = 14 on each side. + // Top/Bottom separation: 4 (ListView padding) + 4 (Card margin) = 8 + // Separator space: 4 (Card margin) + 12 (Separator) + 4 (Card margin) = 20 total space between cards. + + // New ListView.separated padding to compensate for inner Card margins + const EdgeInsets listPadding = + const EdgeInsets.fromLTRB(14, 8, 14, 120 + 4); // 8(L/R) + 6(Card L/R Margin) = 14 + // New separator to match the 12 + 4 * 2 = 20 gap. + const Widget cardSeparator = const SizedBox(height: 12); + const EdgeInsets cardMargin = EdgeInsets.zero; // Margin is now controlled by the ListView.separated padding + + // Internal Card padding matches the live card + const EdgeInsets cardInnerPadding = + const EdgeInsets.symmetric(horizontal: 18, vertical: 14); + // --- End: Configuration to match live UI --- + + return ListView.separated( + padding: listPadding, // Use calculated padding + physics: + const NeverScrollableScrollPhysics(), + itemCount: 4, + separatorBuilder: (_, __) => cardSeparator, // Use calculated separator + itemBuilder: (context, index) { + return Card( + shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(5)), + margin: cardMargin, // Set margin to zero, handled by ListView padding + shadowColor: Colors.indigo.withOpacity(0.10), + color: Colors.white, + child: ShimmerEffect( + child: Padding( + padding: cardInnerPadding, // Use live card's inner padding + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + // 1. Title and Status Row + Row( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + // Project Name Placeholder + Container( + height: 18, // Matches MyText.titleMedium height approx + width: 150, + color: Colors.grey.shade300, + ), + // Status Chip Placeholder + Container( + height: 18, // Matches status chip height approx + width: 60, + decoration: BoxDecoration( + color: Colors.grey.shade300, + borderRadius: BorderRadius.circular(6), + ), + ), + ], + ), + // MySpacing.height(10) in live UI is the key spacing here + // Note: The live UI has MySpacing.height(4) after the title + // and then MySpacing.height(10) before the first detail row, + // so the total space is 4 + 10 = 14. + MySpacing.height(14), + + // 2. Detail Rows (Date, Client, Contact) + // Assigned Date Row + _buildDetailRowSkeleton( + width: 200, iconColor: Colors.teal.shade300), + MySpacing.height(8), + + // Client Row + _buildDetailRowSkeleton( + width: 240, iconColor: Colors.indigo.shade300), + MySpacing.height(8), + + // Contact Row + _buildDetailRowSkeleton( + width: 220, iconColor: Colors.green.shade300), + MySpacing.height(12), // MySpacing.height(12) before Wrap + + // 3. Service Chips Wrap + Wrap( + spacing: 6, + runSpacing: 4, + children: List.generate( + 3, + (chipIndex) => Container( + height: 20, + width: + 70 + (chipIndex * 10).toDouble(), // Varied widths + decoration: BoxDecoration( + color: Colors + .grey.shade300, + borderRadius: BorderRadius.circular(6), + ), + ), + ), + ), + ], + ), + ), + ), + ); + }, + ); +} + +/// Helper to build a skeleton row for details +static Widget _buildDetailRowSkeleton({ + required double width, + required Color iconColor, +}) { + return Row( + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + // Icon Placeholder (size 18 matches live UI) + Icon(Icons.circle, size: 18, color: iconColor), + MySpacing.width(8), + // Text Placeholder (height 13 approx for font size 13) + Container( + height: 14, + width: width, + decoration: BoxDecoration( + color: Colors.grey.shade300, + borderRadius: BorderRadius.circular(4), + ), + ), + ], + ); +} + static Widget attendanceQuickCardSkeleton() { return Container( padding: const EdgeInsets.all(16), diff --git a/lib/view/infraProject/infra_project_screen.dart b/lib/view/infraProject/infra_project_screen.dart index 2ee0495..3895c7b 100644 --- a/lib/view/infraProject/infra_project_screen.dart +++ b/lib/view/infraProject/infra_project_screen.dart @@ -11,6 +11,7 @@ import 'package:on_field_work/model/infra_project/infra_project_list.dart'; import 'package:on_field_work/helpers/widgets/custom_app_bar.dart'; import 'package:on_field_work/view/infraProject/infra_project_details_screen.dart'; +import 'package:on_field_work/helpers/widgets/my_custom_skeleton.dart'; class InfraProjectScreen extends StatefulWidget { const InfraProjectScreen({super.key}); @@ -245,7 +246,7 @@ class _InfraProjectScreenState extends State with UIMixin { Expanded( child: Obx(() { if (controller.isLoading.value) { - return const Center(child: CircularProgressIndicator()); + return Center(child: SkeletonLoaders.serviceProjectListSkeletonLoader()); } final projects = controller.filteredProjects; diff --git a/lib/view/service_project/service_project_screen.dart b/lib/view/service_project/service_project_screen.dart index 24f3b69..b776278 100644 --- a/lib/view/service_project/service_project_screen.dart +++ b/lib/view/service_project/service_project_screen.dart @@ -9,6 +9,7 @@ import 'package:on_field_work/model/service_project/service_projects_list_model. import 'package:on_field_work/helpers/utils/date_time_utils.dart'; import 'package:on_field_work/view/service_project/service_project_details_screen.dart'; import 'package:on_field_work/helpers/widgets/custom_app_bar.dart'; +import 'package:on_field_work/helpers/widgets/my_custom_skeleton.dart'; class ServiceProjectScreen extends StatefulWidget { const ServiceProjectScreen({super.key}); @@ -200,7 +201,7 @@ class _ServiceProjectScreenState extends State end: Alignment.bottomCenter, colors: [ appBarColor, - appBarColor.withOpacity(0.0), + appBarColor.withOpacity(0.0), ], ), ), @@ -264,7 +265,9 @@ class _ServiceProjectScreenState extends State Expanded( child: Obx(() { if (controller.isLoading.value) { - return const Center(child: CircularProgressIndicator()); + return Center( + child: SkeletonLoaders + .serviceProjectListSkeletonLoader()); } final projects = controller.filteredProjects;