Files
CityCards_Customer_Flutter/lib/my_pass/widgets/pass_widget.dart
2026-02-13 15:27:14 +05:30

275 lines
9.5 KiB
Dart

import 'package:flutter/material.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:google_fonts/google_fonts.dart';
import '../models/my_passes_model.dart';
class PassTicketCard extends StatelessWidget {
final MyPassData pass;
const PassTicketCard({super.key, required this.pass});
@override
Widget build(BuildContext context) {
final double cardWidth = MediaQuery.of(context).size.width - 32.w;
final double topSectionHeight = 105.h;
final double bottomSectionHeight = 50.h;
final double cardHeight = topSectionHeight + bottomSectionHeight;
return SizedBox(
width: cardWidth,
child: CustomPaint(
painter: _TicketBackgroundPainter(
cornerRadius: 16.r,
notchRadius: 9.r,
dividerY: topSectionHeight,
borderColor: Colors.white,
shadowColor: Colors.black.withOpacity(0.08),
),
child: ClipPath(
clipper: _TicketClipper(
cornerRadius: 16.r,
notchRadius: 9.r,
dividerY: topSectionHeight,
),
child: Container(
padding: EdgeInsets.symmetric(horizontal: 12.w, vertical: 12.h),
child: Column(
children: [
SizedBox(
height: topSectionHeight - 12.h,
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
ClipRRect(
borderRadius: BorderRadius.circular(10.r),
child: Image.network(
pass.city?.bannerImage ?? '',
height: 80.h,
width: 80.w,
fit: BoxFit.cover,
errorBuilder: (context, error, stackTrace) {
return Container(
height: 80.h,
width: 80.w,
color: Colors.grey[300],
child: Icon(Icons.image, size: 40),
);
},
),
),
SizedBox(width: 10.w),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
if (pass.bookingStatus == "active")
Container(
padding: EdgeInsets.symmetric(
horizontal: 8.w, vertical: 3.h),
decoration: BoxDecoration(
color: const Color(0xff439F6E),
borderRadius: BorderRadius.circular(30.r),
),
child: Text(
"Active",
style: GoogleFonts.poppins(
color: Colors.white,
fontSize: 10.sp,
fontWeight: FontWeight.w400,
),
),
),
SizedBox(width: 8.w),
Text(
"${pass.noOfDays ?? 0} Days",
style: GoogleFonts.poppins(
color: Colors.black87,
fontSize: 12.sp,
),
),
],
),
SizedBox(height: 10.h),
Text(
"${(pass.cardMode?.isNotEmpty ?? false)
? pass.cardMode![0].toUpperCase() + pass.cardMode!.substring(1)
: ''} Card",
style: GoogleFonts.poppins(
fontWeight: FontWeight.w600,
fontSize: 18.sp,
height: 1.1,
),
),
SizedBox(height: 4.h),
Text(
"Adults-${pass.totalAdult ?? 0} • Kids-${pass.totalChild ?? 0}",
style: GoogleFonts.poppins(
color: Colors.black54,
fontSize: 11.sp,
),
),
],
),
),
CircleAvatar(
radius: 20.r,
backgroundColor: Color(0xffFEE7E7),
child: Image.asset(
"assets/images/qr_image.png",
scale: 6,
),
)
],
),
),
SizedBox(height: 15.h),
Padding(
padding: EdgeInsets.symmetric(horizontal: 4.w),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
"Valid Till: ${pass.validUpto ?? ''}",
style: GoogleFonts.poppins(
fontSize: 11.sp,
color: Colors.black,
fontWeight: FontWeight.w400),
),
Text(
pass.city?.name ?? '',
style: GoogleFonts.poppins(
fontWeight: FontWeight.w500,
fontSize: 13.sp,
),
),
],
),
),
],
),
),
),
),
);
}
}
class _TicketClipper extends CustomClipper<Path> {
final double cornerRadius;
final double notchRadius;
final double dividerY;
_TicketClipper({
required this.cornerRadius,
required this.notchRadius,
required this.dividerY,
});
@override
Path getClip(Size size) {
final rrectPath = Path()
..addRRect(RRect.fromRectAndRadius(
Rect.fromLTWH(0, 0, size.width, size.height),
Radius.circular(cornerRadius),
));
final cuts = Path()
..addOval(Rect.fromCircle(
center: Offset(0, dividerY), radius: notchRadius))
..addOval(Rect.fromCircle(
center: Offset(size.width, dividerY), radius: notchRadius));
return Path.combine(PathOperation.difference, rrectPath, cuts);
}
@override
bool shouldReclip(covariant _TicketClipper old) =>
cornerRadius != old.cornerRadius ||
notchRadius != old.notchRadius ||
dividerY != old.dividerY;
}
class _TicketBackgroundPainter extends CustomPainter {
final double cornerRadius;
final double notchRadius;
final double dividerY;
final Color borderColor;
final Color shadowColor;
_TicketBackgroundPainter({
required this.cornerRadius,
required this.notchRadius,
required this.dividerY,
required this.borderColor,
required this.shadowColor,
});
Path _ticketPath(Size size) {
final clipper = _TicketClipper(
cornerRadius: cornerRadius,
notchRadius: notchRadius,
dividerY: dividerY,
);
return clipper.getClip(size);
}
@override
void paint(Canvas canvas, Size size) {
final path = _ticketPath(size);
canvas.save();
canvas.translate(0, 2);
final shadowPaint = Paint()
..color = Colors.black.withOpacity(0.10)
..maskFilter = const MaskFilter.blur(BlurStyle.normal, 6);
canvas.drawPath(path, shadowPaint);
canvas.restore();
final ambientShadowPaint = Paint()
..color = Colors.black.withOpacity(0.04)
..maskFilter = const MaskFilter.blur(BlurStyle.normal, 12);
canvas.drawPath(path, ambientShadowPaint);
final fillPaint = Paint()
..style = PaintingStyle.fill
..color = const Color(0xffFFFBFB);
canvas.drawPath(path, fillPaint);
final strokePaint = Paint()
..style = PaintingStyle.stroke
..strokeWidth = 0.8
..color = const Color(0xffE5E5E5);
canvas.drawPath(path, strokePaint);
final dashPaint = Paint()
..style = PaintingStyle.stroke
..strokeWidth = 1
..color = const Color(0xff787878);
const double dashWidth = 4;
const double dashSpace = 4;
double startX = 12;
final double endX = size.width - 12;
while (startX < endX) {
final double currentEnd = (startX + dashWidth).clamp(0, endX);
canvas.drawLine(
Offset(startX, dividerY),
Offset(currentEnd, dividerY),
dashPaint,
);
startX += dashWidth + dashSpace;
}
}
@override
bool shouldRepaint(covariant _TicketBackgroundPainter oldDelegate) {
return cornerRadius != oldDelegate.cornerRadius ||
notchRadius != oldDelegate.notchRadius ||
dividerY != oldDelegate.dividerY ||
borderColor != oldDelegate.borderColor ||
shadowColor != oldDelegate.shadowColor;
}
}