product and category loader

This commit is contained in:
efrilm
2025-10-25 00:13:43 +07:00
parent 7bcf54c555
commit 754048b565
16 changed files with 712 additions and 6 deletions
@@ -0,0 +1,47 @@
import 'package:flutter/material.dart';
import '../button/button.dart';
class ErrorCard extends StatelessWidget {
final String title;
final String message;
final Function() onTap;
const ErrorCard({
super.key,
required this.title,
required this.message,
required this.onTap,
});
@override
Widget build(BuildContext context) {
return Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(Icons.error_outline, size: 48, color: Colors.red.shade400),
SizedBox(height: 16),
Text(
title,
style: TextStyle(fontSize: 16, fontWeight: FontWeight.w600),
),
SizedBox(height: 8),
Padding(
padding: EdgeInsets.symmetric(horizontal: 32),
child: Text(
message,
textAlign: TextAlign.center,
style: TextStyle(color: Colors.grey.shade600),
),
),
SizedBox(height: 16),
AppElevatedButton.filled(
width: 120,
onPressed: onTap,
label: 'Coba Lagi',
),
],
),
);
}
}
@@ -0,0 +1,110 @@
import 'package:flutter/material.dart';
import '../../../common/extension/extension.dart';
import '../../../common/theme/theme.dart';
import '../../../domain/product/product.dart';
import '../image/image.dart';
import '../spaces/space.dart';
class ProductCard extends StatelessWidget {
final Product product;
const ProductCard({super.key, required this.product});
@override
Widget build(BuildContext context) {
return GestureDetector(
onTap: () {},
child: Container(
decoration: BoxDecoration(
color: AppColor.white,
borderRadius: BorderRadius.circular(8.0),
border: Border.all(color: AppColor.disabled),
),
child: Stack(
children: [
Padding(
padding: const EdgeInsets.all(4.0),
child: Column(
mainAxisAlignment: MainAxisAlignment.start,
children: [
ClipRRect(
borderRadius: BorderRadius.all(Radius.circular(8.0)),
child: product.imageUrl == ""
? Container(
width: double.infinity,
height: 120,
decoration: BoxDecoration(
color: AppColor.disabled.withOpacity(0.4),
),
child: const Icon(
Icons.image,
color: AppColor.textSecondary,
),
)
: AppNetworkImage(
url: product.imageUrl,
fit: BoxFit.fill,
width: double.infinity,
height: 140,
),
),
const Spacer(),
Text(
product.name,
style: AppStyle.md.copyWith(fontWeight: FontWeight.w700),
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
SpaceHeight(4),
Align(
alignment: Alignment.center,
child: Text(
product.price.currencyFormatRp,
style: const TextStyle(
fontWeight: FontWeight.bold,
fontSize: 12,
),
),
),
SpaceHeight(4),
],
),
),
Positioned(
top: 4,
right: 4,
child: Container(
width: 40,
height: 40,
padding: const EdgeInsets.all(6),
decoration: const BoxDecoration(
borderRadius: BorderRadius.all(Radius.circular(9.0)),
color: AppColor.primary,
),
child: Center(
child: Text(
0.toString(),
style: const TextStyle(
color: Colors.white,
fontSize: 20,
fontWeight: FontWeight.bold,
),
),
),
),
),
if (product.isActive == false)
Container(
width: double.infinity,
decoration: BoxDecoration(
borderRadius: BorderRadius.all(Radius.circular(8.0)),
color: AppColor.disabled.withOpacity(0.5),
),
),
],
),
),
);
}
}
@@ -0,0 +1,8 @@
import 'package:cached_network_image/cached_network_image.dart';
import 'package:firebase_crashlytics/firebase_crashlytics.dart';
import 'package:flutter/material.dart';
import 'package:shimmer/shimmer.dart';
import '../../../common/theme/theme.dart';
part 'network_image.dart';
@@ -0,0 +1,77 @@
part of 'image.dart';
class AppNetworkImage extends StatelessWidget {
final String? url;
final double? height;
final double? width;
final double? borderRadius;
final BoxFit? fit;
final bool? isCanZoom;
final VoidCallback? onTap;
const AppNetworkImage({
super.key,
this.url,
this.height,
this.width,
this.borderRadius = 0,
this.fit = BoxFit.cover,
this.isCanZoom = false,
this.onTap,
});
@override
Widget build(BuildContext context) {
Widget customPhoto(
double? heightx,
double? widthx,
BoxFit? fitx,
double? radius,
) {
return CachedNetworkImage(
imageUrl: url.toString(),
memCacheHeight: 120,
memCacheWidth: 120,
placeholder: (context, url) => Shimmer.fromColors(
baseColor: Colors.grey[300]!,
highlightColor: Colors.grey[100]!,
child: Container(
height: height,
width: width,
decoration: BoxDecoration(
color: Colors.grey.shade300,
borderRadius: BorderRadius.circular(radius ?? 0),
),
),
),
errorWidget: (context, url, error) {
FirebaseCrashlytics.instance.recordError(
error,
StackTrace.current,
reason: 'Failed to load image from: $url',
fatal: false,
);
return Container(
width: double.infinity,
height: 120,
decoration: BoxDecoration(
color: AppColor.disabled.withOpacity(0.4),
),
child: const Icon(Icons.image, color: AppColor.disabled),
);
},
height: heightx,
width: widthx,
fit: fitx,
);
}
return GestureDetector(
onTap: onTap,
child: ClipRRect(
borderRadius: BorderRadius.circular(borderRadius!),
child: customPhoto(height, width, BoxFit.fill, borderRadius),
),
);
}
}