import 'package:flutter/material.dart'; import 'package:syncrow_web/utils/color_manager.dart'; class OccupancyPaintItem { final int index; final int value; final DateTime date; const OccupancyPaintItem({ required this.index, required this.value, required this.date, }); } class OccupancyPainter extends CustomPainter { OccupancyPainter({ required this.items, required this.maxValue, this.hoveredItem, }); final List items; final int maxValue; final OccupancyPaintItem? hoveredItem; static const double cellSize = 16.0; @override void paint(Canvas canvas, Size size) { final fillPaint = Paint(); final borderPaint = Paint() ..color = ColorsManager.grayBorder.withValues(alpha: 0.4) ..style = PaintingStyle.stroke; final hoveredBorderPaint = Paint() ..color = Colors.black ..style = PaintingStyle.stroke ..strokeWidth = 1.5; for (final item in items) { final column = item.index ~/ 7; final row = item.index % 7; final x = column * cellSize; final y = row * cellSize; fillPaint.color = _getColor(item.value); final rect = Rect.fromLTWH(x, y, cellSize, cellSize); canvas.drawRect(rect, fillPaint); if (hoveredItem != null && hoveredItem!.index == item.index) { canvas.drawRect(rect, hoveredBorderPaint); } else { _drawDashedLine( canvas, Offset(x, y), Offset(x + cellSize, y), borderPaint, ); _drawDashedLine( canvas, Offset(x, y + cellSize), Offset(x + cellSize, y + cellSize), borderPaint, ); canvas.drawLine(Offset(x, y), Offset(x, y + cellSize), borderPaint); canvas.drawLine(Offset(x + cellSize, y), Offset(x + cellSize, y + cellSize), borderPaint); } } } void _drawDashedLine(Canvas canvas, Offset start, Offset end, Paint paint) { const dashWidth = 2.0; const dashSpace = 4.0; final totalLength = (end - start).distance; final direction = (end - start) / (end - start).distance; var currentLength = 0.0; while (currentLength < totalLength) { final dashStart = start + direction * currentLength; final nextLength = currentLength + dashWidth; final dashEnd = start + direction * (nextLength < totalLength ? nextLength : totalLength); canvas.drawLine(dashStart, dashEnd, paint); currentLength = nextLength + dashSpace; } } Color _getColor(int value) { if (maxValue == 0) return ColorsManager.vividBlue.withValues(alpha: 0); final clampedValue = 0.075 + (1 * value.clamp(0, maxValue) / maxValue); final opacity = value == 0 ? 0 : clampedValue; return ColorsManager.vividBlue.withValues(alpha: opacity.toDouble()); } @override bool shouldRepaint(covariant OccupancyPainter oldDelegate) => oldDelegate.hoveredItem != hoveredItem; }