260 lines
7.8 KiB
Dart
260 lines
7.8 KiB
Dart
import 'dart:math' as math;
|
|
import 'package:flutter/material.dart';
|
|
|
|
import '../../domain/game/game.dart';
|
|
import '../theme/theme.dart';
|
|
|
|
class WheelPainter extends CustomPainter {
|
|
final List<GamePrize> gamePrizes;
|
|
final Color Function(GamePrize prize, int index) getPrizeColor;
|
|
|
|
WheelPainter({required this.gamePrizes, required this.getPrizeColor});
|
|
|
|
@override
|
|
void paint(Canvas canvas, Size size) {
|
|
if (gamePrizes.isEmpty) return;
|
|
|
|
final center = Offset(size.width / 2, size.height / 2);
|
|
final radius = size.width / 2;
|
|
final sectionAngle = 2 * math.pi / gamePrizes.length;
|
|
|
|
// Draw outer white border
|
|
final outerBorderPaint = Paint()
|
|
..color = Colors.white
|
|
..style = PaintingStyle.fill;
|
|
canvas.drawCircle(center, radius, outerBorderPaint);
|
|
|
|
// Draw blue border ring
|
|
final blueBorderPaint = Paint()
|
|
..color = const Color(0xFF3B82F6)
|
|
..style = PaintingStyle.fill;
|
|
canvas.drawCircle(center, radius - 8, blueBorderPaint);
|
|
|
|
// Draw inner white circle
|
|
final innerWhitePaint = Paint()
|
|
..color = Colors.white
|
|
..style = PaintingStyle.fill;
|
|
canvas.drawCircle(center, radius - 20, innerWhitePaint);
|
|
|
|
// Draw sections
|
|
for (int i = 0; i < gamePrizes.length; i++) {
|
|
final prize = gamePrizes[i];
|
|
final startAngle = i * sectionAngle - math.pi / 2;
|
|
|
|
// Section background
|
|
final sectionPaint = Paint()
|
|
..color = getPrizeColor(prize, i)
|
|
..style = PaintingStyle.fill;
|
|
|
|
canvas.drawArc(
|
|
Rect.fromCircle(center: center, radius: radius - 22),
|
|
startAngle,
|
|
sectionAngle,
|
|
true,
|
|
sectionPaint,
|
|
);
|
|
|
|
// Draw icon in each section
|
|
final iconAngle = startAngle + sectionAngle / 2;
|
|
final iconPosition = Offset(
|
|
center.dx + (radius - 60) * math.cos(iconAngle),
|
|
center.dy + (radius - 60) * math.sin(iconAngle),
|
|
);
|
|
|
|
// Draw icon background circle
|
|
final iconBgPaint = Paint()
|
|
..color = Colors.white
|
|
..style = PaintingStyle.fill;
|
|
canvas.drawCircle(iconPosition, 16, iconBgPaint);
|
|
|
|
// Save canvas state for icon drawing
|
|
canvas.save();
|
|
canvas.translate(iconPosition.dx, iconPosition.dy);
|
|
|
|
// Get icon from metadata or use default
|
|
final iconData = _getIconFromMetadata(prize.metadata);
|
|
final iconText = _getIconText(iconData);
|
|
final iconTextPainter = TextPainter(
|
|
text: TextSpan(
|
|
text: iconText,
|
|
style: TextStyle(
|
|
fontFamily: 'MaterialIcons',
|
|
fontSize: 20,
|
|
color: getPrizeColor(prize, i),
|
|
fontWeight: FontWeight.normal,
|
|
),
|
|
),
|
|
textDirection: TextDirection.ltr,
|
|
);
|
|
iconTextPainter.layout();
|
|
iconTextPainter.paint(
|
|
canvas,
|
|
Offset(-iconTextPainter.width / 2, -iconTextPainter.height / 2),
|
|
);
|
|
|
|
canvas.restore();
|
|
|
|
// Draw prize text
|
|
final textAngle = startAngle + sectionAngle / 2;
|
|
final textPosition = Offset(
|
|
center.dx + (radius - 100) * math.cos(textAngle),
|
|
center.dy + (radius - 100) * math.sin(textAngle),
|
|
);
|
|
|
|
// Save canvas state for text rotation
|
|
canvas.save();
|
|
canvas.translate(textPosition.dx, textPosition.dy);
|
|
canvas.rotate(textAngle + math.pi / 2);
|
|
|
|
final textPainter = TextPainter(
|
|
text: TextSpan(
|
|
text: prize.name,
|
|
style: const TextStyle(
|
|
color: Colors.white,
|
|
fontSize: 10,
|
|
fontWeight: FontWeight.bold,
|
|
),
|
|
),
|
|
textDirection: TextDirection.ltr,
|
|
);
|
|
textPainter.layout();
|
|
textPainter.paint(
|
|
canvas,
|
|
Offset(-textPainter.width / 2, -textPainter.height / 2),
|
|
);
|
|
|
|
canvas.restore();
|
|
|
|
// Draw stock indicator if stock is low
|
|
if (prize.stock <= 5 && prize.stock > 0) {
|
|
final stockAngle = startAngle + sectionAngle / 2;
|
|
final stockPosition = Offset(
|
|
center.dx + (radius - 35) * math.cos(stockAngle),
|
|
center.dy + (radius - 35) * math.sin(stockAngle),
|
|
);
|
|
|
|
final stockPaint = Paint()
|
|
..color = AppColor.error
|
|
..style = PaintingStyle.fill;
|
|
canvas.drawCircle(stockPosition, 8, stockPaint);
|
|
|
|
canvas.save();
|
|
canvas.translate(stockPosition.dx, stockPosition.dy);
|
|
|
|
final stockTextPainter = TextPainter(
|
|
text: TextSpan(
|
|
text: '!',
|
|
style: const TextStyle(
|
|
color: Colors.white,
|
|
fontSize: 10,
|
|
fontWeight: FontWeight.bold,
|
|
),
|
|
),
|
|
textDirection: TextDirection.ltr,
|
|
);
|
|
stockTextPainter.layout();
|
|
stockTextPainter.paint(
|
|
canvas,
|
|
Offset(-stockTextPainter.width / 2, -stockTextPainter.height / 2),
|
|
);
|
|
|
|
canvas.restore();
|
|
}
|
|
}
|
|
|
|
// Draw white dots around the outer edge
|
|
final dotPaint = Paint()
|
|
..color = Colors.white
|
|
..style = PaintingStyle.fill;
|
|
|
|
for (int i = 0; i < 24; i++) {
|
|
final dotAngle = (2 * math.pi / 24) * i;
|
|
final dotPosition = Offset(
|
|
center.dx + (radius - 14) * math.cos(dotAngle),
|
|
center.dy + (radius - 14) * math.sin(dotAngle),
|
|
);
|
|
canvas.drawCircle(dotPosition, 3, dotPaint);
|
|
}
|
|
|
|
// Draw section dividers
|
|
final dividerPaint = Paint()
|
|
..color = AppColor.white
|
|
..style = PaintingStyle.stroke
|
|
..strokeWidth = 2;
|
|
|
|
for (int i = 0; i < gamePrizes.length; i++) {
|
|
final angle = i * sectionAngle - math.pi / 2;
|
|
final lineStart = Offset(
|
|
center.dx + (radius - 130) * math.cos(angle),
|
|
center.dy + (radius - 130) * math.sin(angle),
|
|
);
|
|
final lineEnd = Offset(
|
|
center.dx + (radius - 22) * math.cos(angle),
|
|
center.dy + (radius - 22) * math.sin(angle),
|
|
);
|
|
canvas.drawLine(lineStart, lineEnd, dividerPaint);
|
|
}
|
|
}
|
|
|
|
IconData _getIconFromMetadata(Map<String, dynamic> metadata) {
|
|
final iconName = metadata['icon'] as String?;
|
|
switch (iconName?.toLowerCase()) {
|
|
case 'card_giftcard':
|
|
return Icons.card_giftcard;
|
|
case 'monetization_on':
|
|
return Icons.monetization_on;
|
|
case 'redeem':
|
|
return Icons.redeem;
|
|
case 'attach_money':
|
|
return Icons.attach_money;
|
|
case 'account_balance_wallet':
|
|
return Icons.account_balance_wallet;
|
|
case 'diamond':
|
|
return Icons.diamond;
|
|
case 'star':
|
|
return Icons.star;
|
|
case 'emoji_events':
|
|
return Icons.emoji_events;
|
|
case 'refresh':
|
|
return Icons.refresh;
|
|
case 'visibility':
|
|
return Icons.visibility;
|
|
default:
|
|
return Icons.card_giftcard; // Default icon
|
|
}
|
|
}
|
|
|
|
String _getIconText(IconData icon) {
|
|
// Convert IconData to Unicode string for drawing
|
|
switch (icon.codePoint) {
|
|
case 0xe8f4: // Icons.visibility
|
|
return String.fromCharCode(0xe8f4);
|
|
case 0xe8f5: // Icons.visibility_off
|
|
return String.fromCharCode(0xe8f5);
|
|
case 0xe850: // Icons.account_balance_wallet
|
|
return String.fromCharCode(0xe850);
|
|
case 0xe151: // Icons.card_giftcard
|
|
return String.fromCharCode(0xe151);
|
|
case 0xe5d5: // Icons.refresh
|
|
return String.fromCharCode(0xe5d5);
|
|
case 0xe263: // Icons.attach_money
|
|
return String.fromCharCode(0xe263);
|
|
case 0xe8a1: // Icons.redeem
|
|
return String.fromCharCode(0xe8a1);
|
|
case 0xe57d: // Icons.monetization_on
|
|
return String.fromCharCode(0xe57d);
|
|
case 0xe5ca: // Icons.diamond
|
|
return String.fromCharCode(0xe5ca);
|
|
case 0xe838: // Icons.star
|
|
return String.fromCharCode(0xe838);
|
|
case 0xe2a3: // Icons.emoji_events
|
|
return String.fromCharCode(0xe2a3);
|
|
default:
|
|
return String.fromCharCode(0xe151); // Default card_giftcard icon
|
|
}
|
|
}
|
|
|
|
@override
|
|
bool shouldRepaint(covariant CustomPainter oldDelegate) => true;
|
|
}
|