146 lines
4.2 KiB
Dart
146 lines
4.2 KiB
Dart
import 'dart:math' as math;
|
|
|
|
import 'package:aves/model/activity/activity_track.dart';
|
|
import 'package:flutter/material.dart';
|
|
import 'package:latlong2/latlong.dart' show LatLng;
|
|
|
|
class ActivityTrackPreview extends StatelessWidget {
|
|
final ActivityTrack activity;
|
|
final double size;
|
|
|
|
const ActivityTrackPreview({
|
|
super.key,
|
|
required this.activity,
|
|
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: _ActivityTrackPreviewPainter(
|
|
tracks: activity.tracks,
|
|
lineColor: scheme.primary,
|
|
pointColor: scheme.secondary,
|
|
),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
}
|
|
}
|
|
|
|
class _ActivityTrackPreviewPainter extends CustomPainter {
|
|
final Set<List<LatLng>> tracks;
|
|
final Color lineColor;
|
|
final Color pointColor;
|
|
|
|
const _ActivityTrackPreviewPainter({
|
|
required this.tracks,
|
|
required this.lineColor,
|
|
required this.pointColor,
|
|
});
|
|
|
|
@override
|
|
void paint(Canvas canvas, Size size) {
|
|
final allPoints = tracks
|
|
.expand((segment) => segment)
|
|
.where(
|
|
(point) => point.latitude.isFinite && point.longitude.isFinite && point.latitude.abs() <= 90 && point.longitude.abs() <= 180,
|
|
)
|
|
.toList();
|
|
|
|
if (allPoints.isEmpty) {
|
|
_drawEmpty(canvas, size);
|
|
return;
|
|
}
|
|
|
|
final minLat = allPoints.map((p) => p.latitude).reduce((a, b) => a < b ? a : b);
|
|
final maxLat = allPoints.map((p) => p.latitude).reduce((a, b) => a > b ? a : b);
|
|
final minLon = allPoints.map((p) => p.longitude).reduce((a, b) => a < b ? a : b);
|
|
final maxLon = allPoints.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(LatLng 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;
|
|
|
|
for (final segment in tracks) {
|
|
final validSegment = segment
|
|
.where(
|
|
(point) => point.latitude.isFinite && point.longitude.isFinite && point.latitude.abs() <= 90 && point.longitude.abs() <= 180,
|
|
)
|
|
.toList();
|
|
|
|
if (validSegment.length < 2) continue;
|
|
|
|
final first = project(validSegment.first);
|
|
final path = Path()..moveTo(first.dx, first.dy);
|
|
|
|
for (int i = 1; i < validSegment.length; i++) {
|
|
final projected = project(validSegment[i]);
|
|
path.lineTo(projected.dx, projected.dy);
|
|
}
|
|
|
|
canvas.drawPath(path, paint);
|
|
}
|
|
|
|
final start = project(allPoints.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 _ActivityTrackPreviewPainter oldDelegate) {
|
|
return oldDelegate.tracks != tracks || oldDelegate.lineColor != lineColor || oldDelegate.pointColor != pointColor;
|
|
}
|
|
}
|