146 lines
4.2 KiB
Dart
146 lines
4.2 KiB
Dart
import 'dart:math' as math;
|
|
|
|
import 'package:aves/model/activity/activity_preview_point.dart';
|
|
import 'package:flutter/material.dart';
|
|
|
|
class ActivityEntryPreview extends StatelessWidget {
|
|
final List<ActivityPreviewPoint> points;
|
|
final double size;
|
|
|
|
const ActivityEntryPreview({
|
|
super.key,
|
|
required this.points,
|
|
this.size = 64,
|
|
});
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
final scheme = Theme.of(context).colorScheme;
|
|
|
|
return ClipRRect(
|
|
borderRadius: BorderRadius.circular(12),
|
|
child: DecoratedBox(
|
|
decoration: BoxDecoration(
|
|
color: scheme.surfaceContainerHighest,
|
|
border: Border.all(
|
|
color: scheme.outlineVariant,
|
|
),
|
|
),
|
|
child: SizedBox.square(
|
|
dimension: size,
|
|
child: CustomPaint(
|
|
painter: _ActivityEntryPreviewPainter(
|
|
points: points,
|
|
lineColor: scheme.primary,
|
|
pointColor: scheme.secondary,
|
|
),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
}
|
|
}
|
|
|
|
class _ActivityEntryPreviewPainter extends CustomPainter {
|
|
final List<ActivityPreviewPoint> points;
|
|
final Color lineColor;
|
|
final Color pointColor;
|
|
|
|
const _ActivityEntryPreviewPainter({
|
|
required this.points,
|
|
required this.lineColor,
|
|
required this.pointColor,
|
|
});
|
|
|
|
@override
|
|
void paint(Canvas canvas, Size size) {
|
|
final validPoints = points
|
|
.where(
|
|
(point) => point.latitude.isFinite && point.longitude.isFinite && point.latitude.abs() <= 90 && point.longitude.abs() <= 180,
|
|
)
|
|
.toList();
|
|
|
|
if (validPoints.isEmpty) {
|
|
_drawEmpty(canvas, size);
|
|
return;
|
|
}
|
|
|
|
final minLat = validPoints.map((p) => p.latitude).reduce((a, b) => a < b ? a : b);
|
|
final maxLat = validPoints.map((p) => p.latitude).reduce((a, b) => a > b ? a : b);
|
|
final minLon = validPoints.map((p) => p.longitude).reduce((a, b) => a < b ? a : b);
|
|
final maxLon = validPoints.map((p) => p.longitude).reduce((a, b) => a > b ? a : b);
|
|
|
|
final latSpan = math.max(0.000001, maxLat - minLat);
|
|
final lonSpan = math.max(0.000001, maxLon - minLon);
|
|
|
|
const padding = 8.0;
|
|
final drawableWidth = math.max(1.0, size.width - padding * 2);
|
|
final drawableHeight = math.max(1.0, size.height - padding * 2);
|
|
final scale = math.min(drawableWidth / lonSpan, drawableHeight / latSpan);
|
|
|
|
final usedWidth = lonSpan * scale;
|
|
final usedHeight = latSpan * scale;
|
|
final offsetX = padding + (drawableWidth - usedWidth) / 2;
|
|
final offsetY = padding + (drawableHeight - usedHeight) / 2;
|
|
|
|
Offset project(ActivityPreviewPoint point) {
|
|
final x = offsetX + (point.longitude - minLon) * scale;
|
|
final y = offsetY + (maxLat - point.latitude) * scale;
|
|
return Offset(x, y);
|
|
}
|
|
|
|
final paint = Paint()
|
|
..color = lineColor
|
|
..strokeWidth = 2.2
|
|
..style = PaintingStyle.stroke
|
|
..strokeCap = StrokeCap.round
|
|
..strokeJoin = StrokeJoin.round;
|
|
|
|
final grouped = <int, List<ActivityPreviewPoint>>{};
|
|
|
|
for (final point in validPoints) {
|
|
grouped.putIfAbsent(point.segmentIndex, () => []).add(point);
|
|
}
|
|
|
|
for (final segment in grouped.values) {
|
|
segment.sort((a, b) => a.pointIndex.compareTo(b.pointIndex));
|
|
|
|
if (segment.length < 2) continue;
|
|
|
|
final first = project(segment.first);
|
|
final path = Path()..moveTo(first.dx, first.dy);
|
|
|
|
for (int i = 1; i < segment.length; i++) {
|
|
final projected = project(segment[i]);
|
|
path.lineTo(projected.dx, projected.dy);
|
|
}
|
|
|
|
canvas.drawPath(path, paint);
|
|
}
|
|
|
|
final start = project(validPoints.first);
|
|
canvas.drawCircle(
|
|
start,
|
|
3.2,
|
|
Paint()..color = pointColor,
|
|
);
|
|
}
|
|
|
|
void _drawEmpty(Canvas canvas, Size size) {
|
|
final paint = Paint()
|
|
..color = lineColor.withOpacity(0.4)
|
|
..strokeWidth = 1.5
|
|
..style = PaintingStyle.stroke;
|
|
|
|
canvas.drawLine(
|
|
Offset(size.width * 0.25, size.height * 0.50),
|
|
Offset(size.width * 0.75, size.height * 0.50),
|
|
paint,
|
|
);
|
|
}
|
|
|
|
@override
|
|
bool shouldRepaint(covariant _ActivityEntryPreviewPainter oldDelegate) {
|
|
return oldDelegate.points != points || oldDelegate.lineColor != lineColor || oldDelegate.pointColor != pointColor;
|
|
}
|
|
}
|