mirror of
https://github.com/dz0ny/meshcore-sar.git
synced 2026-08-11 16:30:28 +00:00
Add MapLegend widget to display counts of various map elements
- Introduced a new MapLegend widget that shows a legend for team members, found persons, fires, staging areas, and objects on the map. - Each legend item includes an icon, label, and count, styled appropriately. - The widget uses a Card for visual separation and includes padding for better layout.
This commit is contained in:
107
lib/widgets/map/compass_widget.dart
Normal file
107
lib/widgets/map/compass_widget.dart
Normal file
@@ -0,0 +1,107 @@
|
||||
import 'dart:math';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class CompassWidget extends StatelessWidget {
|
||||
final double heading;
|
||||
final bool hasHeading;
|
||||
|
||||
const CompassWidget({
|
||||
super.key,
|
||||
required this.heading,
|
||||
required this.hasHeading,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Card(
|
||||
child: Container(
|
||||
width: 56,
|
||||
height: 56,
|
||||
padding: const EdgeInsets.all(8),
|
||||
child: Stack(
|
||||
alignment: Alignment.center,
|
||||
children: [
|
||||
// Compass rose background - rotates to show true north at top
|
||||
Transform.rotate(
|
||||
angle: heading * pi / 180,
|
||||
child: CustomPaint(
|
||||
size: const Size(40, 40),
|
||||
painter: _CompassRosePainter(),
|
||||
),
|
||||
),
|
||||
// Fixed needle pointing up (since map rotates)
|
||||
Icon(
|
||||
Icons.navigation,
|
||||
color: hasHeading ? Colors.red : Colors.grey,
|
||||
size: 28,
|
||||
),
|
||||
// Heading text
|
||||
Positioned(
|
||||
bottom: 0,
|
||||
child: Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 4, vertical: 1),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.black.withValues(alpha: 0.7),
|
||||
borderRadius: BorderRadius.circular(4),
|
||||
),
|
||||
child: Text(
|
||||
hasHeading ? '${heading.round()}°' : '--',
|
||||
style: const TextStyle(
|
||||
color: Colors.white,
|
||||
fontSize: 9,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _CompassRosePainter extends CustomPainter {
|
||||
@override
|
||||
void paint(Canvas canvas, Size size) {
|
||||
final paint = Paint()
|
||||
..color = Colors.grey.withValues(alpha: 0.3)
|
||||
..style = PaintingStyle.stroke
|
||||
..strokeWidth = 1;
|
||||
|
||||
final center = Offset(size.width / 2, size.height / 2);
|
||||
final radius = size.width / 2;
|
||||
|
||||
// Draw circle
|
||||
canvas.drawCircle(center, radius, paint);
|
||||
|
||||
// Draw cardinal direction markers
|
||||
final textPainter = TextPainter(
|
||||
textDirection: TextDirection.ltr,
|
||||
);
|
||||
|
||||
final directions = ['N', 'E', 'S', 'W'];
|
||||
for (int i = 0; i < 4; i++) {
|
||||
final angle = i * pi / 2 - pi / 2; // Start from North (top)
|
||||
final x = center.dx + radius * 0.7 * cos(angle);
|
||||
final y = center.dy + radius * 0.7 * sin(angle);
|
||||
|
||||
textPainter.text = TextSpan(
|
||||
text: directions[i],
|
||||
style: TextStyle(
|
||||
color: Colors.grey.shade700,
|
||||
fontSize: 10,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
);
|
||||
textPainter.layout();
|
||||
textPainter.paint(
|
||||
canvas,
|
||||
Offset(x - textPainter.width / 2, y - textPainter.height / 2),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
bool shouldRepaint(covariant CustomPainter oldDelegate) => false;
|
||||
}
|
||||
1447
lib/widgets/map/detailed_compass_dialog.dart
Normal file
1447
lib/widgets/map/detailed_compass_dialog.dart
Normal file
File diff suppressed because it is too large
Load Diff
116
lib/widgets/map/map_legend.dart
Normal file
116
lib/widgets/map/map_legend.dart
Normal file
@@ -0,0 +1,116 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class MapLegend extends StatelessWidget {
|
||||
final int teamMemberCount;
|
||||
final int foundPersonCount;
|
||||
final int fireCount;
|
||||
final int stagingAreaCount;
|
||||
final int objectCount;
|
||||
|
||||
const MapLegend({
|
||||
super.key,
|
||||
required this.teamMemberCount,
|
||||
required this.foundPersonCount,
|
||||
required this.fireCount,
|
||||
required this.stagingAreaCount,
|
||||
required this.objectCount,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Card(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(12),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Text(
|
||||
'Legend',
|
||||
style: Theme.of(context).textTheme.titleSmall?.copyWith(
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
_LegendItem(
|
||||
icon: Icons.person,
|
||||
color: Theme.of(context).colorScheme.primary,
|
||||
label: 'Team',
|
||||
count: teamMemberCount,
|
||||
),
|
||||
_LegendItem(
|
||||
icon: Icons.person_pin,
|
||||
color: Colors.green,
|
||||
label: 'Found',
|
||||
count: foundPersonCount,
|
||||
),
|
||||
_LegendItem(
|
||||
icon: Icons.local_fire_department,
|
||||
color: Colors.red,
|
||||
label: 'Fire',
|
||||
count: fireCount,
|
||||
),
|
||||
_LegendItem(
|
||||
icon: Icons.home_work,
|
||||
color: Colors.orange,
|
||||
label: 'Staging',
|
||||
count: stagingAreaCount,
|
||||
),
|
||||
_LegendItem(
|
||||
icon: Icons.inventory_2,
|
||||
color: Colors.purple,
|
||||
label: 'Object',
|
||||
count: objectCount,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _LegendItem extends StatelessWidget {
|
||||
final IconData icon;
|
||||
final Color color;
|
||||
final String label;
|
||||
final int count;
|
||||
|
||||
const _LegendItem({
|
||||
required this.icon,
|
||||
required this.color,
|
||||
required this.label,
|
||||
required this.count,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 4),
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Icon(icon, size: 16, color: color),
|
||||
const SizedBox(width: 8),
|
||||
Text(
|
||||
label,
|
||||
style: Theme.of(context).textTheme.bodySmall,
|
||||
),
|
||||
const SizedBox(width: 4),
|
||||
Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 6, vertical: 1),
|
||||
decoration: BoxDecoration(
|
||||
color: color.withValues(alpha: 0.2),
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
),
|
||||
child: Text(
|
||||
count.toString(),
|
||||
style: Theme.of(context).textTheme.labelSmall?.copyWith(
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user