2025-08-23 16:29:32 +07:00
|
|
|
import 'package:flutter/material.dart';
|
|
|
|
|
import 'dart:math';
|
|
|
|
|
|
|
|
|
|
/// Widget untuk efek kertas struk tersobek di bagian atas
|
|
|
|
|
class ReceiptTearTop extends StatelessWidget {
|
2026-09-04 09:48:34 +07:00
|
|
|
final double? width;
|
|
|
|
|
const ReceiptTearTop({super.key, this.width});
|
2025-08-23 16:29:32 +07:00
|
|
|
|
|
|
|
|
@override
|
|
|
|
|
Widget build(BuildContext context) {
|
2026-09-04 09:48:34 +07:00
|
|
|
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),
|
|
|
|
|
),
|
|
|
|
|
);
|
|
|
|
|
},
|
2025-08-23 16:29:32 +07:00
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Widget untuk efek kertas struk tersobek di bagian bawah
|
|
|
|
|
class ReceiptTearBottom extends StatelessWidget {
|
2026-09-04 09:48:34 +07:00
|
|
|
final double? width;
|
|
|
|
|
const ReceiptTearBottom({super.key, this.width});
|
2025-08-23 16:29:32 +07:00
|
|
|
|
|
|
|
|
@override
|
|
|
|
|
Widget build(BuildContext context) {
|
2026-09-04 09:48:34 +07:00
|
|
|
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),
|
|
|
|
|
),
|
|
|
|
|
);
|
|
|
|
|
},
|
2025-08-23 16:29:32 +07:00
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// 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();
|
|
|
|
|
|
2026-09-04 09:48:34 +07:00
|
|
|
// 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;
|
2025-08-23 16:29:32 +07:00
|
|
|
|
|
|
|
|
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)
|
2026-09-04 09:48:34 +07:00
|
|
|
for (int i = 0; i <= segmentCount; i++) {
|
2025-08-23 16:29:32 +07:00
|
|
|
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)
|
2026-09-04 09:48:34 +07:00
|
|
|
for (int i = 0; i <= segmentCount; i++) {
|
2025-08-23 16:29:32 +07:00
|
|
|
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;
|
|
|
|
|
}
|