import 'package:flutter/material.dart'; import 'dart:math'; /// Widget untuk efek kertas struk tersobek di bagian atas class ReceiptTearTop extends StatelessWidget { final double? width; const ReceiptTearTop({super.key, this.width}); @override Widget build(BuildContext context) { return LayoutBuilder( builder: (context, constraints) { final effectiveWidth = width ?? (constraints.maxWidth.isFinite ? constraints.maxWidth : 360.0); return Container( width: effectiveWidth, color: Colors.grey[300], child: CustomPaint( size: Size(effectiveWidth, 20), painter: _TearPainter(isTop: true), ), ); }, ); } } /// Widget untuk efek kertas struk tersobek di bagian bawah class ReceiptTearBottom extends StatelessWidget { final double? width; const ReceiptTearBottom({super.key, this.width}); @override Widget build(BuildContext context) { return LayoutBuilder( builder: (context, constraints) { final effectiveWidth = width ?? (constraints.maxWidth.isFinite ? constraints.maxWidth : 360.0); return Container( width: effectiveWidth, color: Colors.grey[300], child: CustomPaint( size: Size(effectiveWidth, 20), painter: _TearPainter(isTop: false), ), ); }, ); } } /// CustomPainter untuk menggambar efek sobek class _TearPainter extends CustomPainter { final bool isTop; _TearPainter({required this.isTop}); @override void paint(Canvas canvas, Size size) { final paint = Paint() ..color = Colors.white ..style = PaintingStyle.fill; final path = Path(); // Buat efek robekan dengan pola gigi proporsional terhadap lebar struk final random = Random(123); // Seed tetap agar pola robekan konsisten final segmentCount = (size.width / 18).round().clamp(15, 40); final segmentWidth = size.width / segmentCount; if (isTop) { // Start from top left path.moveTo(0, 0); // Create tear effect with randomized peaks/valleys // For top tear, peaks point upward (out of the paper) for (int i = 0; i <= segmentCount; i++) { final x = i * segmentWidth; // Alternate between peaks and valleys final y = (i % 2 == 0) ? -(2 + random.nextDouble() * 3) // Peak pointing upward (negative Y) : 0.0; // Valley at top edge path.lineTo(x, y); } // Close the path path.lineTo(size.width, 0); path.lineTo(size.width, size.height); path.lineTo(0, size.height); path.close(); } else { // Start from bottom left path.moveTo(0, size.height); // Create tear effect with randomized peaks/valleys // For bottom tear, peaks point downward (out of the paper) for (int i = 0; i <= segmentCount; i++) { final x = i * segmentWidth; // Alternate between peaks and valleys final y = (i % 2 == 0) ? size.height + (2 + random.nextDouble() * 3) // Peak pointing downward : size.height.toDouble(); // Valley at bottom edge path.lineTo(x, y); } // Close the path path.lineTo(size.width, size.height); path.lineTo(size.width, 0); path.lineTo(0, 0); path.close(); } canvas.drawPath(path, paint); } @override bool shouldRepaint(covariant CustomPainter oldDelegate) => false; }