Implementation
String getPresentationWidgetsCustomListWidgetTemplate(String projectName) {
return '''
import 'package:flutter/material.dart';
import '../../core/theme/app_colors.dart';
class ListWidget extends StatelessWidget {
final bool separator;
final bool shrinkWrap;
final int itemCount;
final Widget? emptyChild;
final ScrollPhysics? scrollPhysics;
final Widget Function(BuildContext, int) itemBuilder;
final ScrollController? scrollController;
final bool? reverse;
final Axis? scrollDirection;
final EdgeInsets? padding;
final bool isLoadingMore;
final bool isShimmerLoading;
final int shimmerItemCount;
/// 🔥 NEW → Custom shimmer builder
final Widget Function(BuildContext, int)? shimmerBuilder;
const ListWidget({
super.key,
this.separator = false,
this.shrinkWrap = true,
this.itemCount = 0,
this.emptyChild,
this.scrollPhysics,
this.scrollDirection,
required this.itemBuilder,
this.scrollController,
this.reverse,
this.padding,
this.isLoadingMore = false,
this.isShimmerLoading = false,
this.shimmerItemCount = 6,
this.shimmerBuilder,
});
@override
Widget build(BuildContext context) {
/// -------------------------
/// Shimmer Loading
/// -------------------------
if (isShimmerLoading) {
return ListView.builder(
shrinkWrap: shrinkWrap,
physics: const NeverScrollableScrollPhysics(),
padding: padding ?? EdgeInsets.zero,
itemCount: shimmerItemCount,
itemBuilder: (context, index) {
/// ✅ Use custom shimmer if provided
if (shimmerBuilder != null) {
return shimmerBuilder!(context, index);
}
/// fallback
return const _ShimmerItem();
},
);
}
/// -------------------------
/// Empty State
/// -------------------------
if (itemCount == 0) {
return EmptyWidget(child: emptyChild);
}
final totalCount = isLoadingMore ? itemCount + 1 : itemCount;
return separator == false
? ListView.builder(
controller: scrollController,
shrinkWrap: shrinkWrap,
itemCount: totalCount,
physics: scrollPhysics ?? const AlwaysScrollableScrollPhysics(),
scrollDirection: scrollDirection ?? Axis.vertical,
padding: padding ?? EdgeInsets.zero,
reverse: reverse ?? false,
itemBuilder: (context, index) {
if (index >= itemCount) {
return const _LoaderFooter();
}
return itemBuilder(context, index);
},
)
: ListView.separated(
controller: scrollController,
shrinkWrap: shrinkWrap,
itemCount: totalCount,
physics: scrollPhysics ?? const AlwaysScrollableScrollPhysics(),
scrollDirection: scrollDirection ?? Axis.vertical,
padding: padding ?? EdgeInsets.zero,
reverse: reverse ?? false,
itemBuilder: (context, index) {
if (index >= itemCount) {
return const _LoaderFooter();
}
return itemBuilder(context, index);
},
separatorBuilder: (context, index) {
if (index >= itemCount) return const SizedBox.shrink();
return const Divider(height: 0.1, color: AppColors.borderColor);
},
);
}
}
class GridWidget extends StatelessWidget {
final int itemCount;
final Widget Function(BuildContext, int) itemBuilder;
final double childAspectRatio;
final double crossAxisSpacing;
final double mainAxisSpacing;
final int? crossAxisCount;
final EdgeInsets? padding;
final ScrollController? scrollController;
final ScrollPhysics? scrollPhysics;
final bool shrinkWrap;
final bool isLoadingMore;
/// 🔥 NEW → shimmer support for grid
final bool isShimmerLoading;
final int shimmerItemCount;
final Widget Function(BuildContext, int)? shimmerBuilder;
const GridWidget({
super.key,
required this.itemCount,
required this.itemBuilder,
this.childAspectRatio = 1,
this.crossAxisSpacing = 8,
this.mainAxisSpacing = 8,
this.crossAxisCount,
this.padding,
this.scrollController,
this.scrollPhysics,
this.shrinkWrap = true,
this.isLoadingMore = false,
this.isShimmerLoading = false,
this.shimmerItemCount = 6,
this.shimmerBuilder,
});
@override
Widget build(BuildContext context) {
/// -------------------------
/// Shimmer Loading (GRID)
/// -------------------------
if (isShimmerLoading) {
return GridView.builder(
shrinkWrap: shrinkWrap,
physics: const NeverScrollableScrollPhysics(),
padding: padding ?? EdgeInsets.zero,
itemCount: shimmerItemCount,
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount:
crossAxisCount ??
(MediaQuery.of(context).size.width ~/ 160).clamp(2, 6),
crossAxisSpacing: crossAxisSpacing,
mainAxisSpacing: mainAxisSpacing,
childAspectRatio: childAspectRatio,
),
itemBuilder: (context, index) {
if (shimmerBuilder != null) {
return shimmerBuilder!(context, index);
}
return const _ShimmerItem();
},
);
}
/// -------------------------
/// Empty State
/// -------------------------
if (itemCount == 0) {
return const EmptyWidget(message: "No data found");
}
final totalCount = isLoadingMore ? itemCount + 1 : itemCount;
return GridView.builder(
controller: scrollController,
shrinkWrap: shrinkWrap,
physics: scrollPhysics ?? const AlwaysScrollableScrollPhysics(),
padding: padding ?? EdgeInsets.zero,
itemCount: totalCount,
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount:
crossAxisCount ??
(MediaQuery.of(context).size.width ~/ 160).clamp(2, 6),
crossAxisSpacing: crossAxisSpacing,
mainAxisSpacing: mainAxisSpacing,
childAspectRatio: childAspectRatio,
),
itemBuilder: (context, index) {
if (index >= itemCount) {
return const _LoaderFooter();
}
return itemBuilder(context, index);
},
);
}
}
/// -------------------------
/// Loader Footer Widget
/// -------------------------
class _LoaderFooter extends StatelessWidget {
const _LoaderFooter();
@override
Widget build(BuildContext context) {
return const Padding(
padding: EdgeInsets.all(16.0),
child: Center(
child: CircularProgressIndicator(
strokeWidth: 2,
color: AppColors.primary,
),
),
);
}
}
/// -------------------------
/// Default Shimmer Item
/// -------------------------
class _ShimmerItem extends StatelessWidget {
const _ShimmerItem();
@override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.symmetric(vertical: 8, horizontal: 16),
child: Container(
height: 80,
decoration: BoxDecoration(
color: Colors.grey.shade300,
borderRadius: BorderRadius.circular(10),
),
),
);
}
}
/// -------------------------
/// Empty Widget
/// -------------------------
class EmptyWidget extends StatelessWidget {
final String message;
final IconData icon;
final Widget? child;
const EmptyWidget({
super.key,
this.message = "No items available",
this.icon = Icons.inbox_outlined,
this.child,
});
@override
Widget build(BuildContext context) {
return Center(
child: Column(
mainAxisSize: MainAxisSize.min,
children: [child ?? Container()],
),
);
}
}''';
}