premier app version beta

This commit is contained in:
2026-01-18 13:38:09 +01:00
commit 031d4a4e17
164 changed files with 13698 additions and 0 deletions

View File

@@ -0,0 +1,343 @@
import 'package:flutter/material.dart';
import '../../../core/theme/app_theme.dart';
import '../../../data/models/shot.dart';
import '../../../data/models/target_type.dart';
class TargetOverlay extends StatelessWidget {
final List<Shot> shots;
final double targetCenterX;
final double targetCenterY;
final double targetRadius;
final TargetType targetType;
final int ringCount;
final List<double>? ringRadii; // Individual ring radii multipliers
final void Function(Shot shot)? onShotTapped;
final void Function(double x, double y)? onAddShot;
final double? groupingCenterX;
final double? groupingCenterY;
final double? groupingDiameter;
final List<Shot>? referenceImpacts;
const TargetOverlay({
super.key,
required this.shots,
required this.targetCenterX,
required this.targetCenterY,
required this.targetRadius,
required this.targetType,
this.ringCount = 10,
this.ringRadii,
this.onShotTapped,
this.onAddShot,
this.groupingCenterX,
this.groupingCenterY,
this.groupingDiameter,
this.referenceImpacts,
});
@override
Widget build(BuildContext context) {
return GestureDetector(
onTapUp: (details) {
if (onAddShot != null) {
final RenderBox box = context.findRenderObject() as RenderBox;
final localPosition = details.localPosition;
final relX = localPosition.dx / box.size.width;
final relY = localPosition.dy / box.size.height;
onAddShot!(relX, relY);
}
},
child: CustomPaint(
painter: _TargetOverlayPainter(
shots: shots,
targetCenterX: targetCenterX,
targetCenterY: targetCenterY,
targetRadius: targetRadius,
targetType: targetType,
ringCount: ringCount,
ringRadii: ringRadii,
groupingCenterX: groupingCenterX,
groupingCenterY: groupingCenterY,
groupingDiameter: groupingDiameter,
referenceImpacts: referenceImpacts,
),
child: Stack(
children: shots.map((shot) {
return Positioned(
left: 0,
top: 0,
right: 0,
bottom: 0,
child: LayoutBuilder(
builder: (context, constraints) {
final x = shot.x * constraints.maxWidth;
final y = shot.y * constraints.maxHeight;
return Stack(
children: [
Positioned(
left: x - 15,
top: y - 15,
child: GestureDetector(
onTap: () => onShotTapped?.call(shot),
child: Container(
width: 30,
height: 30,
decoration: BoxDecoration(
color: Colors.transparent,
shape: BoxShape.circle,
),
),
),
),
],
);
},
),
);
}).toList(),
),
),
);
}
}
class _TargetOverlayPainter extends CustomPainter {
final List<Shot> shots;
final double targetCenterX;
final double targetCenterY;
final double targetRadius;
final TargetType targetType;
final int ringCount;
final List<double>? ringRadii;
final double? groupingCenterX;
final double? groupingCenterY;
final double? groupingDiameter;
final List<Shot>? referenceImpacts;
_TargetOverlayPainter({
required this.shots,
required this.targetCenterX,
required this.targetCenterY,
required this.targetRadius,
required this.targetType,
this.ringCount = 10,
this.ringRadii,
this.groupingCenterX,
this.groupingCenterY,
this.groupingDiameter,
this.referenceImpacts,
});
@override
void paint(Canvas canvas, Size size) {
// Draw target center indicator
_drawTargetCenter(canvas, size);
// Draw grouping circle
if (groupingCenterX != null && groupingCenterY != null && groupingDiameter != null && shots.length > 1) {
_drawGroupingCircle(canvas, size);
}
// Draw impacts
for (final shot in shots) {
_drawImpact(canvas, size, shot);
}
// Draw reference impacts (with different color)
if (referenceImpacts != null) {
for (final ref in referenceImpacts!) {
_drawReferenceImpact(canvas, size, ref);
}
}
}
void _drawTargetCenter(Canvas canvas, Size size) {
final centerX = targetCenterX * size.width;
final centerY = targetCenterY * size.height;
final minDim = size.width < size.height ? size.width : size.height;
final maxRadius = targetRadius * minDim;
final strokePaint = Paint()
..color = Colors.green.withValues(alpha: 0.5)
..style = PaintingStyle.stroke
..strokeWidth = 1;
// Draw concentric rings based on ringCount (with individual radii if provided)
for (int i = 0; i < ringCount; i++) {
final ringMultiplier = (ringRadii != null && ringRadii!.length == ringCount)
? ringRadii![i]
: (i + 1) / ringCount;
final ringRadius = maxRadius * ringMultiplier;
canvas.drawCircle(Offset(centerX, centerY), ringRadius, strokePaint);
}
// Draw score labels on rings (only if within visible area)
final textPainter = TextPainter(
textDirection: TextDirection.ltr,
);
for (int i = 0; i < ringCount; i++) {
// Calculate zone center (midpoint between this ring and previous)
final currentMultiplier = (ringRadii != null && ringRadii!.length == ringCount)
? ringRadii![i]
: (i + 1) / ringCount;
final prevMultiplier = i == 0
? 0.0
: (ringRadii != null && ringRadii!.length == ringCount)
? ringRadii![i - 1]
: i / ringCount;
final zoneRadius = maxRadius * (currentMultiplier + prevMultiplier) / 2;
final score = 10 - i;
// Only draw label if it's within the visible area
final labelX = centerX + zoneRadius;
if (labelX < 0 || labelX > size.width) continue;
textPainter.text = TextSpan(
text: '$score',
style: TextStyle(
color: Colors.green.withValues(alpha: 0.8),
fontSize: 10,
fontWeight: FontWeight.bold,
shadows: const [
Shadow(color: Colors.black, blurRadius: 2),
],
),
);
textPainter.layout();
// Draw label on the right side of each zone
final labelY = centerY - textPainter.height / 2;
if (labelY >= 0 && labelY <= size.height) {
textPainter.paint(canvas, Offset(labelX - textPainter.width / 2, labelY));
}
}
// Draw crosshair at center
final crosshairPaint = Paint()
..color = Colors.green.withValues(alpha: 0.7)
..strokeWidth = 1;
canvas.drawLine(
Offset(centerX - 10, centerY),
Offset(centerX + 10, centerY),
crosshairPaint,
);
canvas.drawLine(
Offset(centerX, centerY - 10),
Offset(centerX, centerY + 10),
crosshairPaint,
);
}
void _drawGroupingCircle(Canvas canvas, Size size) {
final centerX = groupingCenterX! * size.width;
final centerY = groupingCenterY! * size.height;
final diameter = groupingDiameter! * size.width.clamp(0, size.height);
// Draw filled circle
final fillPaint = Paint()
..color = AppTheme.groupingCircleColor
..style = PaintingStyle.fill;
canvas.drawCircle(Offset(centerX, centerY), diameter / 2, fillPaint);
// Draw outline
final strokePaint = Paint()
..color = AppTheme.groupingCenterColor
..style = PaintingStyle.stroke
..strokeWidth = 2;
canvas.drawCircle(Offset(centerX, centerY), diameter / 2, strokePaint);
// Draw center point
final centerPaint = Paint()
..color = AppTheme.groupingCenterColor
..style = PaintingStyle.fill;
canvas.drawCircle(Offset(centerX, centerY), 4, centerPaint);
}
void _drawImpact(Canvas canvas, Size size, Shot shot) {
final x = shot.x * size.width;
final y = shot.y * size.height;
// Draw outer circle (white outline for visibility)
final outlinePaint = Paint()
..color = AppTheme.impactOutlineColor
..style = PaintingStyle.stroke
..strokeWidth = 3;
canvas.drawCircle(Offset(x, y), 10, outlinePaint);
// Draw impact marker
final impactPaint = Paint()
..color = AppTheme.impactColor
..style = PaintingStyle.fill;
canvas.drawCircle(Offset(x, y), 8, impactPaint);
// Draw score number
final textPainter = TextPainter(
text: TextSpan(
text: '${shot.score}',
style: const TextStyle(
color: Colors.white,
fontSize: 10,
fontWeight: FontWeight.bold,
),
),
textDirection: TextDirection.ltr,
);
textPainter.layout();
textPainter.paint(
canvas,
Offset(x - textPainter.width / 2, y - textPainter.height / 2),
);
}
void _drawReferenceImpact(Canvas canvas, Size size, Shot ref) {
final x = ref.x * size.width;
final y = ref.y * size.height;
// Draw outer circle (white outline for visibility)
final outlinePaint = Paint()
..color = Colors.white
..style = PaintingStyle.stroke
..strokeWidth = 3;
canvas.drawCircle(Offset(x, y), 12, outlinePaint);
// Draw reference marker (purple)
final refPaint = Paint()
..color = Colors.deepPurple
..style = PaintingStyle.fill;
canvas.drawCircle(Offset(x, y), 10, refPaint);
// Draw "R" to indicate reference
final textPainter = TextPainter(
text: const TextSpan(
text: 'R',
style: TextStyle(
color: Colors.white,
fontSize: 12,
fontWeight: FontWeight.bold,
),
),
textDirection: TextDirection.ltr,
);
textPainter.layout();
textPainter.paint(
canvas,
Offset(x - textPainter.width / 2, y - textPainter.height / 2),
);
}
@override
bool shouldRepaint(covariant _TargetOverlayPainter oldDelegate) {
return shots != oldDelegate.shots ||
targetCenterX != oldDelegate.targetCenterX ||
targetCenterY != oldDelegate.targetCenterY ||
targetRadius != oldDelegate.targetRadius ||
ringCount != oldDelegate.ringCount ||
ringRadii != oldDelegate.ringRadii ||
groupingCenterX != oldDelegate.groupingCenterX ||
groupingCenterY != oldDelegate.groupingCenterY ||
groupingDiameter != oldDelegate.groupingDiameter ||
referenceImpacts != oldDelegate.referenceImpacts;
}
}