feat(tactical-app): full sovereign military suite in Flutter with first-run onboarding, artillery ballistics, HLZ assessment, minefield breach, isochrone, symbols, and overlays

This commit is contained in:
Hamza-Ayed
2026-08-22 15:24:09 +03:00
parent c10fd2396c
commit 5e3be672ee
23 changed files with 4096 additions and 1028 deletions
@@ -5,8 +5,15 @@ import '../services/military_grid_utils.dart';
enum MapPickerTarget {
losObserver,
losTarget,
viewshedCenter,
routeOrigin,
routeDestination,
artilleryGun,
artilleryTarget,
hlzCenter,
minefieldStart,
minefieldEnd,
isochroneCenter,
}
class InteractiveMapPickerHud extends StatelessWidget {
@@ -29,23 +36,46 @@ class InteractiveMapPickerHud extends StatelessWidget {
return 'تحديد موقع الراصد الميداني (Observer) 🔴';
case MapPickerTarget.losTarget:
return 'تحديد موقع الهدف التكتيكي (Target) 🎯';
case MapPickerTarget.viewshedCenter:
return 'تحديد مركز الرصد الدائري 360° (Radar) 📡';
case MapPickerTarget.routeOrigin:
return 'تحديد نقطة الانطلاق (البداية) 🟢';
case MapPickerTarget.routeDestination:
return 'تحديد نقطة الوصول والهدف (الوجهة) 🏁';
case MapPickerTarget.artilleryGun:
return 'تحديد مربض المدفعية (Battery Position) 🎯';
case MapPickerTarget.artilleryTarget:
return 'تحديد الهدف المعادي للمدفعية (Target) 💥';
case MapPickerTarget.hlzCenter:
return 'تحديد موقع مهبط المروحيات (HLZ) 🚁';
case MapPickerTarget.minefieldStart:
return 'تحديد بداية حقل الألغام (Point A) ⚠️';
case MapPickerTarget.minefieldEnd:
return 'تحديد نهاية حقل الألغام (Point B) ⚠️';
case MapPickerTarget.isochroneCenter:
return 'تحديد قاعدة قوة التدخل السريع (QRF) ⚡';
}
}
Color get themeColor {
switch (target) {
case MapPickerTarget.losObserver:
case MapPickerTarget.viewshedCenter:
return const Color(0xFF38BDF8);
case MapPickerTarget.losTarget:
case MapPickerTarget.artilleryTarget:
return const Color(0xFFEF4444);
case MapPickerTarget.routeOrigin:
case MapPickerTarget.hlzCenter:
return const Color(0xFF22C55E);
case MapPickerTarget.routeDestination:
case MapPickerTarget.minefieldStart:
case MapPickerTarget.minefieldEnd:
return const Color(0xFFF59E0B);
case MapPickerTarget.artilleryGun:
return const Color(0xFF0071E3);
case MapPickerTarget.isochroneCenter:
return const Color(0xFFA855F7);
}
}
@@ -54,184 +84,165 @@ class InteractiveMapPickerHud extends StatelessWidget {
case MapPickerTarget.losObserver:
return Icons.person_pin_circle;
case MapPickerTarget.losTarget:
case MapPickerTarget.artilleryTarget:
return Icons.gps_fixed;
case MapPickerTarget.viewshedCenter:
return Icons.radar;
case MapPickerTarget.routeOrigin:
return Icons.my_location;
case MapPickerTarget.routeDestination:
return Icons.flag;
case MapPickerTarget.artilleryGun:
return Icons.adjust;
case MapPickerTarget.hlzCenter:
return Icons.flight_land;
case MapPickerTarget.minefieldStart:
case MapPickerTarget.minefieldEnd:
return Icons.warning_amber;
case MapPickerTarget.isochroneCenter:
return Icons.timelapse;
}
}
@override
Widget build(BuildContext context) {
final milCoords = MilitaryGridUtils.fromLatLng(centerPosition);
final mgrs = MilitaryGridUtils.latLngToMgrs(centerPosition.latitude, centerPosition.longitude);
return Stack(
children: [
// ── 1. Center Floating Marker & Tooltip (Siro Rider Style) ──
// ── 1. Center Crosshair / Pointer ─────────────────────────
Center(
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
// Dynamic Floating Coordinates Bubble
Container(
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 6),
padding: const EdgeInsets.all(8),
decoration: BoxDecoration(
color: const Color(0xF20F172A),
borderRadius: BorderRadius.circular(20),
border: Border.all(color: themeColor, width: 1.5),
boxShadow: const [BoxShadow(color: Colors.black54, blurRadius: 15)],
),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Text(
'شرقيات: ${milCoords.eastingStr} م • شماليات: ${milCoords.northingStr} م',
style: const TextStyle(color: Colors.white, fontSize: 11, fontWeight: FontWeight.bold),
),
Text(
'${centerPosition.latitude.toStringAsFixed(5)}, ${centerPosition.longitude.toStringAsFixed(5)}',
style: const TextStyle(color: Color(0xFF94A3B8), fontSize: 9.5),
color: const Color(0xFF090E17),
shape: BoxShape.circle,
border: Border.all(color: themeColor, width: 2),
boxShadow: [
BoxShadow(
color: themeColor.withAlpha(120),
blurRadius: 16,
spreadRadius: 2,
),
],
),
child: Icon(pinIcon, color: themeColor, size: 28),
),
const SizedBox(height: 6),
// Animated Floating Pin Icon
Icon(pinIcon, size: 44, color: themeColor),
// Ground Shadow Reticle
const SizedBox(height: 2),
// Pointer Dot
Container(
width: 10,
height: 5,
width: 6,
height: 6,
decoration: BoxDecoration(
color: Colors.black.withAlpha(128),
borderRadius: BorderRadius.circular(10),
color: Colors.white,
shape: BoxShape.circle,
border: Border.all(color: themeColor, width: 1.5),
),
),
const SizedBox(height: 48), // Offset to position pin tip at exact screen center
],
),
),
// ── 2. Top Instructions Bar ─────────────────────────────────
// ── 2. Top Instructions & MGRS Banner ───────────────────────
Positioned(
top: 50,
left: 20,
right: 20,
top: 60,
left: 16,
right: 16,
child: Container(
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 10),
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12),
decoration: BoxDecoration(
color: const Color(0xF20F172A),
color: const Color(0xFF090E17).withAlpha(240),
borderRadius: BorderRadius.circular(14),
border: Border.all(color: Colors.white12),
boxShadow: const [BoxShadow(color: Colors.black54, blurRadius: 12)],
border: Border.all(color: themeColor.withAlpha(120), width: 1.2),
boxShadow: const [
BoxShadow(color: Colors.black87, blurRadius: 16, spreadRadius: 2),
],
),
child: Row(
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Icon(Icons.touch_app, color: themeColor, size: 20),
const SizedBox(width: 10),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
title,
style: const TextStyle(color: Colors.white, fontSize: 12.5, fontWeight: FontWeight.bold),
),
const Text(
'حرك الخريطة وضع رأس المؤشر على النقطة المطلوبة بدقة',
style: TextStyle(color: Color(0xFF94A3B8), fontSize: 10),
),
],
Text(
title,
style: const TextStyle(
color: Colors.white,
fontSize: 13,
fontWeight: FontWeight.bold,
),
textAlign: TextAlign.center,
),
IconButton(
icon: const Icon(Icons.close, color: Colors.white70, size: 18),
onPressed: onCancel,
const SizedBox(height: 4),
Text(
'حرّك الخريطة وضع النقطة في مركز المؤشر ثم اضغط "تثبيت"',
style: TextStyle(
color: Colors.white.withAlpha(180),
fontSize: 10.5,
),
textAlign: TextAlign.center,
),
const SizedBox(height: 6),
Container(
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 3),
decoration: BoxDecoration(
color: Colors.white10,
borderRadius: BorderRadius.circular(6),
),
child: Text(
'MGRS: $mgrs',
style: TextStyle(
color: themeColor,
fontSize: 11,
fontFamily: 'monospace',
fontWeight: FontWeight.bold,
),
),
),
],
),
),
),
// ── 3. Bottom Confirmation Action Bar ────────────────────────
// ── 3. Bottom Confirm / Cancel Actions ─────────────────────
Positioned(
bottom: 24,
left: 16,
right: 16,
child: Container(
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: const Color(0xF50F172A),
borderRadius: BorderRadius.circular(20),
border: Border.all(color: themeColor, width: 1.5),
boxShadow: const [BoxShadow(color: Colors.black87, blurRadius: 25)],
),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
const Text('الموقع المحدد حالياً:', style: TextStyle(color: Color(0xFF94A3B8), fontSize: 11)),
Text(
'المربع: ${milCoords.shortGrid} (${milCoords.zone}N)',
style: TextStyle(color: themeColor, fontSize: 11, fontWeight: FontWeight.bold),
),
],
),
const SizedBox(height: 6),
Container(
width: double.infinity,
padding: const EdgeInsets.all(8),
decoration: BoxDecoration(
color: const Color(0xFF1E293B),
borderRadius: BorderRadius.circular(10),
bottom: 30,
left: 20,
right: 20,
child: Row(
children: [
Expanded(
child: ElevatedButton.icon(
style: ElevatedButton.styleFrom(
backgroundColor: themeColor,
foregroundColor: Colors.white,
padding: const EdgeInsets.symmetric(vertical: 14),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12),
),
child: Text(
milCoords.arabicFullFormat,
textAlign: TextAlign.center,
style: const TextStyle(color: Colors.white, fontSize: 12, fontWeight: FontWeight.bold),
elevation: 8,
),
icon: const Icon(Icons.check, size: 20),
label: const Text(
'تثبيت النقطة في الميدان',
style: TextStyle(fontSize: 13, fontWeight: FontWeight.bold),
),
onPressed: onConfirm,
),
const SizedBox(height: 12),
Row(
children: [
Expanded(
flex: 3,
child: ElevatedButton.icon(
onPressed: onConfirm,
icon: const Icon(Icons.check, size: 18),
label: const Text('تثبيت وتأكيد هذا الموقع', style: TextStyle(fontSize: 13, fontWeight: FontWeight.bold)),
style: ElevatedButton.styleFrom(
backgroundColor: const Color(0xFF0071E3),
foregroundColor: Colors.white,
padding: const EdgeInsets.symmetric(vertical: 13),
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
),
),
),
const SizedBox(width: 8),
Expanded(
flex: 1,
child: OutlinedButton(
onPressed: onCancel,
style: OutlinedButton.styleFrom(
foregroundColor: const Color(0xFF94A3B8),
side: const BorderSide(color: Colors.white24),
padding: const EdgeInsets.symmetric(vertical: 13),
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
),
child: const Text('إلغاء', style: TextStyle(fontSize: 12)),
),
),
],
),
const SizedBox(width: 12),
Container(
decoration: BoxDecoration(
color: const Color(0xFF0F172A),
borderRadius: BorderRadius.circular(12),
border: Border.all(color: Colors.white24),
),
],
),
child: IconButton(
icon: const Icon(Icons.close, color: Colors.white70),
onPressed: onCancel,
),
),
],
),
),
],