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,232 @@
import 'dart:math' as math;
import 'dart:ui' as ui;
import 'package:flutter/material.dart';
import '../../../services/statistics_service.dart';
class HeatMapWidget extends StatelessWidget {
final HeatMap heatMap;
final double size;
const HeatMapWidget({
super.key,
required this.heatMap,
this.size = 250,
});
@override
Widget build(BuildContext context) {
if (heatMap.zones.isEmpty || heatMap.totalShots == 0) {
return SizedBox(
width: size,
height: size,
child: const Center(
child: Text('Aucune donnee'),
),
);
}
return Container(
width: size,
height: size,
decoration: BoxDecoration(
border: Border.all(color: Colors.grey.shade300),
borderRadius: BorderRadius.circular(8),
),
child: ClipRRect(
borderRadius: BorderRadius.circular(7),
child: CustomPaint(
size: Size(size, size),
painter: _HeatMapFogPainter(heatMap: heatMap),
),
),
);
}
}
class _HeatMapFogPainter extends CustomPainter {
final HeatMap heatMap;
_HeatMapFogPainter({required this.heatMap});
@override
void paint(Canvas canvas, Size size) {
if (heatMap.zones.isEmpty) return;
// Draw base background (cold blue)
final bgPaint = Paint()
..color = const Color(0xFF1A237E).withValues(alpha: 0.3); // Dark blue base
canvas.drawRect(Rect.fromLTWH(0, 0, size.width, size.height), bgPaint);
final cellWidth = size.width / heatMap.gridSize;
final cellHeight = size.height / heatMap.gridSize;
// Collect all shot positions with their intensities for fog effect
final hotSpots = <_HotSpot>[];
for (int row = 0; row < heatMap.gridSize; row++) {
for (int col = 0; col < heatMap.gridSize; col++) {
final zone = heatMap.zones[row][col];
if (zone.shotCount > 0) {
hotSpots.add(_HotSpot(
x: (col + 0.5) * cellWidth,
y: (row + 0.5) * cellHeight,
intensity: zone.intensity,
shotCount: zone.shotCount,
));
}
}
}
// Draw fog effect using radial gradients for each hot spot
for (final spot in hotSpots) {
_drawFogSpot(canvas, size, spot, cellWidth, cellHeight);
}
// Draw target overlay (concentric circles)
final center = Offset(size.width / 2, size.height / 2);
final maxRadius = size.width / 2;
final circlePaint = Paint()
..color = Colors.white.withValues(alpha: 0.5)
..style = PaintingStyle.stroke
..strokeWidth = 1;
for (int i = 1; i <= 5; i++) {
canvas.drawCircle(center, maxRadius * (i / 5), circlePaint);
}
// Draw crosshair
canvas.drawLine(
Offset(center.dx, 0),
Offset(center.dx, size.height),
circlePaint,
);
canvas.drawLine(
Offset(0, center.dy),
Offset(size.width, center.dy),
circlePaint,
);
// Draw shot counts
for (final spot in hotSpots) {
final textPainter = TextPainter(
text: TextSpan(
text: '${spot.shotCount}',
style: TextStyle(
color: Colors.white,
fontSize: 14,
fontWeight: FontWeight.bold,
shadows: [
Shadow(color: Colors.black.withValues(alpha: 0.8), blurRadius: 4),
Shadow(color: Colors.black.withValues(alpha: 0.8), blurRadius: 2),
],
),
),
textDirection: TextDirection.ltr,
);
textPainter.layout();
textPainter.paint(
canvas,
Offset(spot.x - textPainter.width / 2, spot.y - textPainter.height / 2),
);
}
}
void _drawFogSpot(Canvas canvas, Size size, _HotSpot spot, double cellWidth, double cellHeight) {
// Calculate fog radius based on intensity and cell size
final baseRadius = math.max(cellWidth, cellHeight) * 1.5;
final radius = baseRadius * (0.5 + spot.intensity * 0.5);
// Create gradient from hot (red/orange) to transparent
final gradient = ui.Gradient.radial(
Offset(spot.x, spot.y),
radius,
[
_getHeatColor(spot.intensity).withValues(alpha: 0.7 * spot.intensity + 0.3),
_getHeatColor(spot.intensity * 0.5).withValues(alpha: 0.3 * spot.intensity),
Colors.transparent,
],
[0.0, 0.5, 1.0],
);
final paint = Paint()
..shader = gradient
..blendMode = BlendMode.screen; // Additive blending for fog effect
canvas.drawCircle(Offset(spot.x, spot.y), radius, paint);
// Add a second layer for more intensity
if (spot.intensity > 0.3) {
final innerGradient = ui.Gradient.radial(
Offset(spot.x, spot.y),
radius * 0.6,
[
_getHeatColor(spot.intensity).withValues(alpha: 0.5 * spot.intensity),
Colors.transparent,
],
[0.0, 1.0],
);
final innerPaint = Paint()
..shader = innerGradient
..blendMode = BlendMode.screen;
canvas.drawCircle(Offset(spot.x, spot.y), radius * 0.6, innerPaint);
}
}
Color _getHeatColor(double intensity) {
// Gradient from blue (cold) to red (hot)
if (intensity <= 0) return const Color(0xFF2196F3); // Blue
if (intensity >= 1) return const Color(0xFFFF1744); // Red
// Interpolate between blue -> cyan -> yellow -> orange -> red
if (intensity < 0.25) {
// Blue to Cyan
return Color.lerp(
const Color(0xFF2196F3), // Blue
const Color(0xFF00BCD4), // Cyan
intensity / 0.25,
)!;
} else if (intensity < 0.5) {
// Cyan to Yellow
return Color.lerp(
const Color(0xFF00BCD4), // Cyan
const Color(0xFFFFEB3B), // Yellow
(intensity - 0.25) / 0.25,
)!;
} else if (intensity < 0.75) {
// Yellow to Orange
return Color.lerp(
const Color(0xFFFFEB3B), // Yellow
const Color(0xFFFF9800), // Orange
(intensity - 0.5) / 0.25,
)!;
} else {
// Orange to Red
return Color.lerp(
const Color(0xFFFF9800), // Orange
const Color(0xFFFF1744), // Red
(intensity - 0.75) / 0.25,
)!;
}
}
@override
bool shouldRepaint(covariant _HeatMapFogPainter oldDelegate) {
return heatMap != oldDelegate.heatMap;
}
}
class _HotSpot {
final double x;
final double y;
final double intensity;
final int shotCount;
_HotSpot({
required this.x,
required this.y,
required this.intensity,
required this.shotCount,
});
}