refactor(tactical-app): modernize state management with GetX controllers, bindings, and clean MVP architecture

This commit is contained in:
Hamza-Ayed
2026-08-22 15:27:53 +03:00
parent 5e3be672ee
commit 17e8f8eeac
16 changed files with 1855 additions and 1296 deletions
@@ -0,0 +1,43 @@
import 'package:get/get.dart';
import '../controllers/artillery_controller.dart';
import '../controllers/hlz_controller.dart';
import '../controllers/isochrone_controller.dart';
import '../controllers/los_controller.dart';
import '../controllers/minefield_controller.dart';
import '../controllers/navigation_controller.dart';
import '../controllers/overlays_controller.dart';
import '../controllers/resection_controller.dart';
import '../controllers/symbols_controller.dart';
import '../controllers/tactical_map_controller.dart';
import '../controllers/viewshed_controller.dart';
/// ============================================================================
/// [TacticalBinding] - تهيئة وحقن التبعيات والمتحكمات (GetX Dependency Injection)
/// ============================================================================
/// English:
/// Master GetX Binding registering all tactical operation controllers
/// for seamless dependency injection and lifecycle management across the app.
///
/// العربية:
/// مسجل التبعيات المركزي لـ GetX، يقوم بإنشاء وحقن كافة المتحكمات التكتيكية
/// لضمان استقرار دورة حياة التطبيق وإمكانية الوصول إليها من أي شاشة أو نافذة.
/// ============================================================================
class TacticalBinding extends Bindings {
@override
void dependencies() {
// 1. Core Tactical Sub-Controllers
Get.lazyPut<ArtilleryController>(() => ArtilleryController(), fenix: true);
Get.lazyPut<HlzController>(() => HlzController(), fenix: true);
Get.lazyPut<MinefieldController>(() => MinefieldController(), fenix: true);
Get.lazyPut<IsochroneController>(() => IsochroneController(), fenix: true);
Get.lazyPut<SymbolsController>(() => SymbolsController(), fenix: true);
Get.lazyPut<OverlaysController>(() => OverlaysController(), fenix: true);
Get.lazyPut<LosController>(() => LosController(), fenix: true);
Get.lazyPut<ViewshedController>(() => ViewshedController(), fenix: true);
Get.lazyPut<ResectionController>(() => ResectionController(), fenix: true);
Get.lazyPut<NavigationController>(() => NavigationController(), fenix: true);
// 2. Master Map Orchestrator Controller
Get.lazyPut<TacticalMapController>(() => TacticalMapController(), fenix: true);
}
}
@@ -0,0 +1,90 @@
import 'package:get/get.dart';
import 'package:intaleq_maps/intaleq_maps.dart';
import '../models/military_operations_models.dart';
import '../services/artillery_ballistics_engine.dart';
/// ============================================================================
/// [ArtilleryController] - وحدة التحكم برماية المدفعية وقوس القذيفة
/// ============================================================================
/// English:
/// GetX Controller managing artillery firing mission parameters, ballistic
/// trajectory calculations, weapon profiles, and crest clearance checks.
///
/// العربية:
/// متحكم GetX لإدارة معايير رماية المدفعية الميدانية، حلول الرماية البالستية،
/// اختيار أنظمة السلاح، وفحص سلامة المسار من الاصطدام بقمم الجبال (Crest Clearance).
/// ============================================================================
class ArtilleryController extends GetxController {
// ── Observables / الحالات التفاعلية ──────────────────────────────────────
final Rx<LatLng?> gunPosition = Rx<LatLng?>(null);
final Rx<LatLng?> targetPosition = Rx<LatLng?>(null);
final Rx<ArtilleryWeaponSystem> selectedWeapon =
Rx<ArtilleryWeaponSystem>(ArtilleryWeaponSystem.standardSystems.first);
final RxBool highAngle = false.obs;
final RxBool isLoading = false.obs;
final Rx<ArtilleryFiringSolution?> firingSolution =
Rx<ArtilleryFiringSolution?>(null);
/// Set Battery Gun Position / تعيين موقع مربض المدفعية
void setGunPosition(LatLng pos) {
gunPosition.value = pos;
calculateFiringSolution();
}
/// Set Enemy Target Position / تعيين موقع الهدف المعادي
void setTargetPosition(LatLng pos) {
targetPosition.value = pos;
calculateFiringSolution();
}
/// Swap Gun and Target Positions / تبديل مربض المدفعية مع الهدف
void swapPositions() {
final temp = gunPosition.value;
gunPosition.value = targetPosition.value;
targetPosition.value = temp;
calculateFiringSolution();
}
/// Select Weapon Profile / اختيار نوع نظام المدفعية أو الهاون
void selectWeapon(ArtilleryWeaponSystem weapon) {
selectedWeapon.value = weapon;
calculateFiringSolution();
}
/// Toggle High-Angle Trajectory / التبديل بين قوس الرماية المنخفض والعالي
void toggleHighAngle(bool value) {
highAngle.value = value;
calculateFiringSolution();
}
/// Calculate Ballistic Firing Solution / احتساب الحل البالستي الشامل
Future<void> calculateFiringSolution() async {
if (gunPosition.value == null || targetPosition.value == null) {
firingSolution.value = null;
return;
}
try {
isLoading.value = true;
final sol = await ArtilleryBallisticsEngine.calculateFireMission(
weapon: selectedWeapon.value,
gunPos: gunPosition.value!,
targetPos: targetPosition.value!,
highAngle: highAngle.value,
);
firingSolution.value = sol;
} catch (e) {
firingSolution.value = null;
} finally {
isLoading.value = false;
}
}
/// Clear artillery state / إعادة ضبط رماية المدفعية
void reset() {
gunPosition.value = null;
targetPosition.value = null;
firingSolution.value = null;
highAngle.value = false;
}
}
@@ -0,0 +1,73 @@
import 'package:get/get.dart';
import 'package:intaleq_maps/intaleq_maps.dart';
import '../models/military_operations_models.dart';
import '../services/hlz_assessment_engine.dart';
/// ============================================================================
/// [HlzController] - وحدة التحكم بتقييم مهابط الطيران العامودي
/// ============================================================================
/// English:
/// GetX Controller managing Helicopter Landing Zone (HLZ) parameters, terrain
/// slope gradient calculations, approach corridor clearances, and suitability scoring.
///
/// العربية:
/// متحكم GetX لإدارة تقييم مهابط المروحيات العسكرية، فحص نسبة انحدار الأرض،
/// سلامة ممرات الاقتراب والإقلاع، وتحديد درجة صلاحية المهبط تكتيكياً.
/// ============================================================================
class HlzController extends GetxController {
// ── Observables / الحالات التفاعلية ──────────────────────────────────────
final Rx<LatLng?> selectedPosition = Rx<LatLng?>(null);
final Rx<HelicopterType> helicopterType =
Rx<HelicopterType>(HelicopterType.mediumLift);
final RxDouble approachAzimuthDeg = 0.0.obs;
final RxBool isLoading = false.obs;
final Rx<HlzAssessmentResult?> assessmentResult =
Rx<HlzAssessmentResult?>(null);
/// Set Landing Pad Center Location / تعيين موقع مركز المهبط المقترح
void setPosition(LatLng pos) {
selectedPosition.value = pos;
assessLandingZone();
}
/// Select Helicopter Airframe Type / تحديد فئة الطوافة (خفيفة / متوسطة / ثقيلة)
void setHelicopterType(HelicopterType type) {
helicopterType.value = type;
assessLandingZone();
}
/// Set Approach Corridor Azimuth / تحديد سمت ممر الاقتراب
void setApproachAzimuth(double azimuthDeg) {
approachAzimuthDeg.value = azimuthDeg;
assessLandingZone();
}
/// Run Full HLZ Assessment / تنفيذ التقييم الميداني الشامل للمهبط
Future<void> assessLandingZone() async {
if (selectedPosition.value == null) {
assessmentResult.value = null;
return;
}
try {
isLoading.value = true;
final res = await HlzAssessmentEngine.assessLandingZone(
center: selectedPosition.value!,
helicopterType: helicopterType.value,
approachAzimuthDeg: approachAzimuthDeg.value,
);
assessmentResult.value = res;
} catch (e) {
assessmentResult.value = null;
} finally {
isLoading.value = false;
}
}
/// Reset HLZ state / إعادة ضبط المهبط
void reset() {
selectedPosition.value = null;
assessmentResult.value = null;
approachAzimuthDeg.value = 0.0;
}
}
@@ -0,0 +1,61 @@
import 'package:get/get.dart';
import 'package:intaleq_maps/intaleq_maps.dart';
import '../services/tactical_isochrone_engine.dart';
/// ============================================================================
/// [IsochroneController] - وحدة التحكم بنطاق التدخل السريع وزمن الاستجابة
/// ============================================================================
/// English:
/// GetX Controller managing Quick Reaction Force (QRF) and emergency reachability
/// isochrone polygons with terrain slope resistance factors.
///
/// العربية:
/// متحكم GetX لإدارة مضلعات زمن الاستجابة لقوات التدخل السريع (QRF) والإسعاف،
/// مع احتساب تأثير انحدار التضاريس ومقاومة حركة الآليات.
/// ============================================================================
class IsochroneController extends GetxController {
// ── Observables / الحالات التفاعلية ──────────────────────────────────────
final Rx<LatLng?> center = Rx<LatLng?>(null);
final RxDouble speedKmh = 60.0.obs;
final RxBool isLoading = false.obs;
final RxList<IsochroneRing> isochroneRings = <IsochroneRing>[].obs;
/// Set QRF Base Location / تعيين مركز انطلاق قوة التدخل السريع
void setCenter(LatLng pos) {
center.value = pos;
calculateIsochrones();
}
/// Set Average Movement Speed / ضبط متوسط سرعة الآليات (كم/ساعة)
void setSpeed(double speed) {
speedKmh.value = speed;
calculateIsochrones();
}
/// Compute Multi-tier Reachability Rings / احتساب مضلعات الوصول (5، 10، 15 دقيقة)
Future<void> calculateIsochrones() async {
if (center.value == null) {
isochroneRings.clear();
return;
}
try {
isLoading.value = true;
final rings = await TacticalIsochroneEngine.calculateIsochrones(
center: center.value!,
baseSpeedKmh: speedKmh.value,
);
isochroneRings.assignAll(rings);
} catch (e) {
isochroneRings.clear();
} finally {
isLoading.value = false;
}
}
/// Reset isochrone state / إعادة ضبط نطاق الوصول
void reset() {
center.value = null;
isochroneRings.clear();
}
}
@@ -0,0 +1,81 @@
import 'package:get/get.dart';
import 'package:intaleq_maps/intaleq_maps.dart';
import '../services/offline_los_engine.dart';
/// ============================================================================
/// [LosController] - وحدة التحكم بتبادل الرؤية والمقطع التضاريسي (LOS)
/// ============================================================================
/// English:
/// GetX Controller managing Line of Sight (LOS) observer & target coordinates,
/// elevation sampling, terrain obstruction detection, and cross-section reports.
///
/// العربية:
/// متحكم GetX لإدارة تبادل الرؤية والمراقبة (LOS)، استخراج المقطع التضاريسي،
/// كشف نقاط الحجب الجبلي والنقاط الميتة (Dead Ground) بالارتفاعات الحقيقية.
/// ============================================================================
class LosController extends GetxController {
// ── Observables / الحالات التفاعلية ──────────────────────────────────────
final Rx<LatLng?> observerPosition = Rx<LatLng?>(null);
final Rx<LatLng?> targetPosition = Rx<LatLng?>(null);
final RxDouble observerHeightM = 2.0.obs;
final RxDouble targetHeightM = 2.0.obs;
final RxBool isLoading = false.obs;
final Rx<OfflineLosReport?> losReport = Rx<OfflineLosReport?>(null);
/// Set Observer Position / تعيين موقع الراصد الميداني
void setObserver(LatLng pos) {
observerPosition.value = pos;
computeLos();
}
/// Set Target Position / تعيين موقع الهدف التكتيكي
void setTarget(LatLng pos) {
targetPosition.value = pos;
computeLos();
}
/// Swap Observer & Target / تبديل موقع الراصد والهدف
void swapPositions() {
final temp = observerPosition.value;
observerPosition.value = targetPosition.value;
targetPosition.value = temp;
computeLos();
}
/// Set Eye and Target Heights / ضبط ارتفاع العين والهدف بالمتر
void setHeights({double? obsHeight, double? tgtHeight}) {
if (obsHeight != null) observerHeightM.value = obsHeight;
if (tgtHeight != null) targetHeightM.value = tgtHeight;
computeLos();
}
/// Compute High-Precision LOS Report / احتساب تقرير تبادل الرؤية التضاريسي
Future<void> computeLos() async {
if (observerPosition.value == null || targetPosition.value == null) {
losReport.value = null;
return;
}
try {
isLoading.value = true;
final rep = OfflineLosEngine.calculate(
observer: observerPosition.value!,
target: targetPosition.value!,
observerHeightM: observerHeightM.value,
targetHeightM: targetHeightM.value,
);
losReport.value = rep;
} catch (e) {
losReport.value = null;
} finally {
isLoading.value = false;
}
}
/// Reset LOS state / إعادة ضبط خط الرؤية
void reset() {
observerPosition.value = null;
targetPosition.value = null;
losReport.value = null;
}
}
@@ -0,0 +1,81 @@
import 'package:get/get.dart';
import 'package:intaleq_maps/intaleq_maps.dart';
import '../models/military_operations_models.dart';
import '../services/minefield_engine.dart';
/// ============================================================================
/// [MinefieldController] - وحدة التحكم بحقول الألغام وممرات الثغرات
/// ============================================================================
/// English:
/// GetX Controller managing minefield zone geometry, barrier types, threat
/// envelope generation, mine density estimation, and safe breaching corridor calculations.
///
/// العربية:
/// متحكم GetX لإدارة هندسة حقول الألغام والموانع، تصنيف نوع الألغام،
/// حساب الكثافة التقديرية، وتخطيط ممرات الثغرات الآمنة (Breaching Lanes).
/// ============================================================================
class MinefieldController extends GetxController {
// ── Observables / الحالات التفاعلية ──────────────────────────────────────
final Rx<LatLng?> startPoint = Rx<LatLng?>(null);
final Rx<LatLng?> endPoint = Rx<LatLng?>(null);
final Rx<MinefieldType> minefieldType =
Rx<MinefieldType>(MinefieldType.antiTank);
final RxDouble widthMeters = 200.0.obs;
final Rx<MinefieldZoneResult?> zoneResult = Rx<MinefieldZoneResult?>(null);
/// Set Minefield Start Point (A) / تعيين بداية حقل الألغام
void setStartPoint(LatLng pos) {
startPoint.value = pos;
calculateZone();
}
/// Set Minefield End Point (B) / تعيين نهاية حقل الألغام
void setEndPoint(LatLng pos) {
endPoint.value = pos;
calculateZone();
}
/// Swap Start and End Points / تبديل نقاط بداية ونهاية الحقل
void swapPoints() {
final temp = startPoint.value;
startPoint.value = endPoint.value;
endPoint.value = temp;
calculateZone();
}
/// Select Minefield Type / تحديد نوع المانع (ضد دروع / ضد أفراد / مركب)
void setType(MinefieldType type) {
minefieldType.value = type;
calculateZone();
}
/// Set Frontage Depth / تحديد عمق الحقل بالمتر
void setWidth(double width) {
widthMeters.value = width;
calculateZone();
}
/// Calculate Minefield Threat Box & Breaching Corridor / حساب منطقة الخطر وممر الثغرة
void calculateZone() {
if (startPoint.value == null || endPoint.value == null) {
zoneResult.value = null;
return;
}
final res = MinefieldEngine.calculateMinefieldZone(
startPoint: startPoint.value!,
endPoint: endPoint.value!,
type: minefieldType.value,
widthMeters: widthMeters.value,
);
zoneResult.value = res;
}
/// Reset minefield state / إعادة ضبط حقل الألغام
void reset() {
startPoint.value = null;
endPoint.value = null;
zoneResult.value = null;
}
}
@@ -0,0 +1,99 @@
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:intaleq_maps/intaleq_maps.dart';
import '../services/offline_routing_engine.dart';
import '../services/tactical_api_service.dart';
import '../services/turn_by_turn_navigation_engine.dart';
/// ============================================================================
/// [NavigationController] - وحدة التحكم بالملاحة وتوجيه القوافل التكتيكية
/// ============================================================================
/// English:
/// GetX Controller managing hybrid tactical convoy routing (Online API + Sovereign
/// Offline Engine fallback), active turn-by-turn guidance, and navigation metrics.
///
/// العربية:
/// متحكم GetX لإدارة توجيه القوافل والآليات العسكرية (هجين: سيرفر أونلاين + محرك أوفلاين محلي)،
/// وتتبع مسار الملاحة خطوة بخطوة وحساب السرعة والوقت المتبقي للهدف.
/// ============================================================================
class NavigationController extends GetxController {
// ── Observables / الحالات التفاعلية ──────────────────────────────────────
final Rx<OfflineRoutePlan?> activeRoute = Rx<OfflineRoutePlan?>(null);
final Rx<TacticalVehicleProfile> vehicleProfile =
Rx<TacticalVehicleProfile>(TacticalVehicleProfile.convoy);
final RxBool isCalculating = false.obs;
/// Calculate Hybrid Tactical Route / احتساب مسار تكتيكي هجين (سيرفر + محلي)
Future<OfflineRoutePlan?> calculateRoute({
required LatLng origin,
required LatLng destination,
TacticalVehicleProfile? profile,
bool autoStart = false,
IntaleqMapController? mapController,
}) async {
final activeProfile = profile ?? vehicleProfile.value;
vehicleProfile.value = activeProfile;
isCalculating.value = true;
OfflineRoutePlan? plan;
// 1. Try Online NestJS Server Routing API
try {
final serverRoute = await TacticalApiService.calculateTacticalRoute(
origin: origin,
destination: destination,
profile: activeProfile.name,
);
if (serverRoute != null && serverRoute.points.isNotEmpty) {
plan = OfflineRoutePlan(
polylinePoints: serverRoute.points,
totalDistanceKm: serverRoute.distanceKm,
estimatedDurationMinutes: serverRoute.durationMinutes,
profile: activeProfile,
tacticalWaypoints: const ['موقع الانطلاق', 'الهدف التكتيكي المحدد'],
isOffline: false,
);
}
} catch (e) {
debugPrint('Online route fallback: $e');
}
// 2. Fallback to 100% Sovereign On-Device Offline Routing Engine
plan ??= OfflineRoutingEngine.calculateOnDeviceRoute(
start: origin,
destination: destination,
profile: activeProfile,
);
activeRoute.value = plan;
isCalculating.value = false;
if (autoStart) {
startNavigation(mapController: mapController);
}
return plan;
}
/// Start Active Turn-by-Turn Guidance / بدء الإرشاد الملاحي الحي
void startNavigation({IntaleqMapController? mapController}) {
if (activeRoute.value == null) return;
TurnByTurnNavigationEngine.startNavigation(
plan: activeRoute.value!,
simulate: false,
controller: mapController,
);
}
/// Stop Navigation / إنهاء الملاحة
void stopNavigation() {
TurnByTurnNavigationEngine.stopNavigation();
}
/// Reset Route State / إعادة ضبط المسار
void reset() {
stopNavigation();
activeRoute.value = null;
}
}
@@ -0,0 +1,85 @@
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:intaleq_maps/intaleq_maps.dart';
import '../models/military_operations_models.dart';
/// ============================================================================
/// [OverlaysController] - وحدة التحكم بمنظومة الشفافات العسكرية التكتيكية (IPB)
/// ============================================================================
/// English:
/// GetX Controller managing Intelligence Preparation of Battlefield (IPB)
/// transparent overlays, FEBA lines, mobility corridors, and No-Fire Areas.
///
/// العربية:
/// متحكم GetX لإدارة منظومة الشفافات العسكرية التكتيكية (IPB)،
/// والخطوط الأمامية لمنطقة القتال (FEBA)، وممرات الحركة، ومناطق حظر الرماية.
/// ============================================================================
class OverlaysController extends GetxController {
// ── Observables / الحالات التفاعلية ──────────────────────────────────────
final RxList<TacticalOverlayLayer> overlayLayers = <TacticalOverlayLayer>[
TacticalOverlayLayer(
id: 'layer_feba',
nameAr: 'الخط الأمامي لمنطقة القتال (FEBA / FLOT)',
color: const Color(0xFF0071E3),
isVisible: true,
lines: [
[
const LatLng(32.1000, 35.7500),
const LatLng(32.0200, 35.8500),
const LatLng(31.9500, 35.9000),
const LatLng(31.8500, 35.9800),
]
],
),
TacticalOverlayLayer(
id: 'layer_mobility',
nameAr: 'ممرات الحركة ومحاور التقدم (Mobility Corridors)',
color: const Color(0xFF10B981),
isVisible: true,
lines: [
[
const LatLng(31.9539, 35.9106),
const LatLng(32.0000, 35.9500),
const LatLng(32.0500, 36.0200),
]
],
),
TacticalOverlayLayer(
id: 'layer_no_fire',
nameAr: 'مناطق الحظر وعدم الرماية (No-Fire Areas - NFA)',
color: const Color(0xFFEF4444),
isVisible: false,
polygons: [
[
const LatLng(31.9800, 35.9300),
const LatLng(32.0000, 35.9300),
const LatLng(32.0000, 35.9600),
const LatLng(31.9800, 35.9600),
const LatLng(31.9800, 35.9300),
]
],
),
].obs;
/// Toggle Overlay Layer Visibility / تبديل إظهار أو إخفاء طبقة معينة
void toggleLayer(String layerId, bool isVisible) {
final idx = overlayLayers.indexWhere((l) => l.id == layerId);
if (idx != -1) {
overlayLayers[idx] = overlayLayers[idx].copyWith(isVisible: isVisible);
}
}
/// Show All Overlay Layers / إظهار كافة الشفافات
void showAll() {
for (int i = 0; i < overlayLayers.length; i++) {
overlayLayers[i] = overlayLayers[i].copyWith(isVisible: true);
}
}
/// Hide All Overlay Layers / إخفاء كافة الشفافات
void hideAll() {
for (int i = 0; i < overlayLayers.length; i++) {
overlayLayers[i] = overlayLayers[i].copyWith(isVisible: false);
}
}
}
@@ -0,0 +1,46 @@
import 'package:get/get.dart';
import '../models/landmark.dart';
import '../services/resection_calculator.dart';
/// ============================================================================
/// [ResectionController] - وحدة التحكم بالتقاطع البصري العكسي (GPS-Denied Fix)
/// ============================================================================
/// English:
/// GetX Controller managing camera-based optical resection observations,
/// magnetic azimuth readings, and inverse triangulation coordinate resolution.
///
/// العربية:
/// متحكم GetX لإدارة عملية التقاطع البصري العكسي عبر الكاميرا والبوصلة المغناطيسية،
/// واستخراج إحداثيات الموقع بدقة عند انقطاع الـ GPS أو التشويش الإلكتروني.
/// ============================================================================
class ResectionController extends GetxController {
// ── Observables / الحالات التفاعلية ──────────────────────────────────────
final RxList<ResectionObservation> observations = <ResectionObservation>[].obs;
final Rx<ResectionResult?> resectionResult = Rx<ResectionResult?>(null);
final RxDouble currentHeadingDeg = 0.0.obs;
/// Add Observation / إضافة رصد اتجاهي لمعلم جغرافي
void addObservation(ResectionObservation obs) {
observations.removeWhere((o) => o.landmark.id == obs.landmark.id);
observations.add(obs);
}
/// Remove Observation / إزالة رصد معلم معين
void removeObservation(String landmarkId) {
observations.removeWhere((o) => o.landmark.id == landmarkId);
}
/// Compute Position from Observations / استخراج الإحداثيات بالتقاطع العكسي
ResectionResult? computePosition() {
if (observations.length < 2) return null;
final res = ResectionCalculator.calculatePosition(observations);
resectionResult.value = res;
return res;
}
/// Reset all observations / تصفير وإعادة ضبط الرصد
void reset() {
observations.clear();
resectionResult.value = null;
}
}
@@ -0,0 +1,80 @@
import 'package:get/get.dart';
import 'package:intaleq_maps/intaleq_maps.dart';
import '../models/military_operations_models.dart';
/// ============================================================================
/// [SymbolsController] - وحدة التحكم بالرموز والتشكيلات العسكرية
/// ============================================================================
/// English:
/// GetX Controller managing Mil-Std-2525C tactical military symbology palette,
/// interactive placement on the map canvas, echelon levels, and unit lists.
///
/// العربية:
/// متحكم GetX لإدارة لوحة الرموز العسكرية القياسية (Mil-Std-2525C)،
/// وتثبيت الوحدات الميدانية والصديقة والمعادية على الخريطة التكتيكية.
/// ============================================================================
class SymbolsController extends GetxController {
// ── Observables / الحالات التفاعلية ──────────────────────────────────────
final Rx<TacticalSymbolType?> activePlacementType = Rx<TacticalSymbolType?>(null);
final RxList<TacticalSymbolItem> placedSymbols = <TacticalSymbolItem>[
TacticalSymbolItem(
id: 'sym-default-1',
name: 'كتيبة المشاة الآلية 4',
type: TacticalSymbolType.friendlyInfantry,
position: const LatLng(31.9800, 35.8800),
echelon: MilitaryEchelon.battalion,
),
TacticalSymbolItem(
id: 'sym-default-2',
name: 'مربض مدفعية M109',
type: TacticalSymbolType.friendlyArtillery,
position: const LatLng(31.9300, 35.9100),
echelon: MilitaryEchelon.battalion,
),
TacticalSymbolItem(
id: 'sym-default-3',
name: 'رادار كشف تكتيكي',
type: TacticalSymbolType.friendlyRadar,
position: const LatLng(32.0200, 35.8400),
echelon: MilitaryEchelon.company,
),
].obs;
/// Select Symbol for Map Placement Mode / اختيار رمز لتثبيته على الخريطة
void selectSymbolForPlacement(TacticalSymbolType type) {
activePlacementType.value = type;
}
/// Cancel Placement Mode / إلغاء وضع التثبيت
void cancelPlacement() {
activePlacementType.value = null;
}
/// Place Unit at Coordinate / تثبيت الرمز في موقع جغرافي محدد
void placeSymbolAt(LatLng position, {String? customName}) {
if (activePlacementType.value == null) return;
final type = activePlacementType.value!;
final name = customName ?? 'وحدة ميدانية ${placedSymbols.length + 1}';
placedSymbols.add(TacticalSymbolItem(
id: 'sym_${DateTime.now().millisecondsSinceEpoch}',
name: name,
type: type,
position: position,
));
activePlacementType.value = null;
}
/// Remove Placed Symbol / إزالة رمز مثبت من الميدان
void removeSymbol(String symbolId) {
placedSymbols.removeWhere((item) => item.id == symbolId);
}
/// Clear All Symbols / مسح كافة الرموز العسكرية
void clearAll() {
placedSymbols.clear();
activePlacementType.value = null;
}
}
@@ -0,0 +1,605 @@
import 'package:flutter/material.dart';
import 'package:geolocator/geolocator.dart';
import 'package:get/get.dart';
import 'package:intaleq_maps/intaleq_maps.dart';
import '../models/angle_unit.dart';
import '../models/navigation_state.dart';
import '../services/military_grid_utils.dart';
import '../services/offline_los_engine.dart';
import '../widgets/interactive_map_picker_hud.dart';
import 'artillery_controller.dart';
import 'hlz_controller.dart';
import 'isochrone_controller.dart';
import 'los_controller.dart';
import 'minefield_controller.dart';
import 'navigation_controller.dart';
import 'overlays_controller.dart';
import 'resection_controller.dart';
import 'symbols_controller.dart';
import 'viewshed_controller.dart';
/// ============================================================================
/// [TacticalMapController] - المحرك الرئيسي لمنظومة الخريطة والعمليات الميدانية
/// ============================================================================
/// English:
/// Master GetX Controller orchestrating tactical modes, map camera state,
/// declarative layers (Markers, Polylines, Polygons), and crosshair picker HUDs.
///
/// العربية:
/// المتحكم المركزي لمنظومة العمليات التكتيكية، يربط بين مختلف المتحكمات الفرعية
/// (المدفعية، المهابط، الألغام، الشفافات، الرموز) وينظم طبقات الخريطة والمؤشر التفاعلي.
/// ============================================================================
class TacticalMapController extends GetxController {
// ── Sub-Controllers / المتحكمات الفرعية ──────────────────────────────────
final ArtilleryController artillery = Get.find<ArtilleryController>();
final HlzController hlz = Get.find<HlzController>();
final MinefieldController minefield = Get.find<MinefieldController>();
final IsochroneController isochrone = Get.find<IsochroneController>();
final SymbolsController symbols = Get.find<SymbolsController>();
final OverlaysController overlays = Get.find<OverlaysController>();
final LosController los = Get.find<LosController>();
final ViewshedController viewshed = Get.find<ViewshedController>();
final ResectionController resection = Get.find<ResectionController>();
final NavigationController navigation = Get.find<NavigationController>();
// ── Observables / الحالات التفاعلية ──────────────────────────────────────
final RxString currentTacticalMode = 'nav'.obs;
final Rx<AngleUnit> angleUnit = AngleUnit.dual.obs;
final RxBool showContours = true.obs;
final Rx<LatLng?> currentGpsPosition = Rx<LatLng?>(null);
final Rx<LatLng> currentCameraCenter = const LatLng(31.9539, 35.9106).obs;
final Rx<MapPickerTarget?> activePickerTarget = Rx<MapPickerTarget?>(null);
IntaleqMapController? mapController;
@override
void onInit() {
super.onInit();
initGps();
}
/// Initialize Live GPS Sensor / تهيئة حساس الموقع الجغرافي
Future<void> initGps() async {
try {
final serviceEnabled = await Geolocator.isLocationServiceEnabled();
if (!serviceEnabled) return;
LocationPermission perm = await Geolocator.checkPermission();
if (perm == LocationPermission.denied) {
perm = await Geolocator.requestPermission();
}
if (perm == LocationPermission.whileInUse || perm == LocationPermission.always) {
final pos = await Geolocator.getCurrentPosition(
locationSettings: const LocationSettings(
accuracy: LocationAccuracy.high,
timeLimit: Duration(seconds: 5),
),
);
currentGpsPosition.value = LatLng(pos.latitude, pos.longitude);
currentCameraCenter.value = currentGpsPosition.value!;
mapController?.animateCamera(
CameraUpdate.newLatLngZoom(currentGpsPosition.value!, 14.0),
);
}
} catch (e) {
debugPrint('GPS init error: $e');
}
}
/// Switch Tactical Operation Mode / تبديل نمط العملية التكتيكية
void switchMode(String mode) {
currentTacticalMode.value = mode;
}
/// Set Angle Unit Format / تبديل نظام قياس الزوايا والسمت (درجات / ميل)
void setAngleUnit(AngleUnit unit) {
angleUnit.value = unit;
}
/// Toggle Topographic Contours / إظهار أو إخفاء خطوط الكنتور
void toggleContours(bool visible) {
showContours.value = visible;
}
/// Set Active Picker Target / تفعيل المؤشر التفاعلي لاختيار نقطة على الخريطة
void setPickerTarget(MapPickerTarget? target) {
activePickerTarget.value = target;
}
/// Handle Map Click Tap Events / معالجة النقر على الخريطة
void handleMapTap(LatLng point) {
// 1. Check if placing a tactical symbol
if (symbols.activePlacementType.value != null) {
symbols.placeSymbolAt(point);
Get.snackbar(
'الرموز العسكرية',
'تم تثبيت الرمز في الميدان بنجاح ✅',
backgroundColor: const Color(0xFF22C55E),
colorText: Colors.white,
snackPosition: SnackPosition.BOTTOM,
);
return;
}
// 2. Check if active picker HUD is open
if (activePickerTarget.value != null) {
confirmPicker(activePickerTarget.value!, point);
return;
}
// 3. Mode-specific quick tap handlers
switch (currentTacticalMode.value) {
case 'viewshed_360':
viewshed.setObserver(point);
break;
case 'artillery':
if (artillery.gunPosition.value == null) {
artillery.setGunPosition(point);
} else {
artillery.setTargetPosition(point);
}
break;
case 'hlz':
hlz.setPosition(point);
break;
case 'minefield':
if (minefield.startPoint.value == null) {
minefield.setStartPoint(point);
} else {
minefield.setEndPoint(point);
}
break;
case 'isochrone':
isochrone.setCenter(point);
break;
case 'los':
if (los.observerPosition.value == null) {
los.setObserver(point);
} else {
los.setTarget(point);
}
break;
default:
final mil = MilitaryGridUtils.fromLatLng(point);
final elev = JordanDemSurface.elevationAt(point.latitude, point.longitude);
Get.snackbar(
'الإحداثيات العسكرية',
'${mil.arabicFullFormat} • منسوب: ${elev.round()}م',
backgroundColor: const Color(0xFF0F172A),
colorText: Colors.white,
snackPosition: SnackPosition.BOTTOM,
duration: const Duration(seconds: 3),
);
break;
}
}
/// Confirm Point Selection from Crosshair HUD / تأكيد النقطة المحددة بالمؤشر
void confirmPicker(MapPickerTarget target, LatLng pos) {
activePickerTarget.value = null;
switch (target) {
case MapPickerTarget.losObserver:
los.setObserver(pos);
break;
case MapPickerTarget.losTarget:
los.setTarget(pos);
break;
case MapPickerTarget.viewshedCenter:
viewshed.setObserver(pos);
break;
case MapPickerTarget.artilleryGun:
artillery.setGunPosition(pos);
break;
case MapPickerTarget.artilleryTarget:
artillery.setTargetPosition(pos);
break;
case MapPickerTarget.hlzCenter:
hlz.setPosition(pos);
break;
case MapPickerTarget.minefieldStart:
minefield.setStartPoint(pos);
break;
case MapPickerTarget.minefieldEnd:
minefield.setEndPoint(pos);
break;
case MapPickerTarget.isochroneCenter:
isochrone.setCenter(pos);
break;
case MapPickerTarget.routeOrigin:
case MapPickerTarget.routeDestination:
break;
}
}
/// Cancel Picker / إلغاء وضع المؤشر
void cancelPicker() {
activePickerTarget.value = null;
}
/// Fit Map Camera to Bounding Box of Points / تحريك الكاميرا لتحتوي النقاط
void fitBounds(List<LatLng> points) {
if (points.isEmpty || mapController == null) return;
double minLat = points.first.latitude;
double maxLat = points.first.latitude;
double minLng = points.first.longitude;
double maxLng = points.first.longitude;
for (final p in points) {
if (p.latitude < minLat) minLat = p.latitude;
if (p.latitude > maxLat) maxLat = p.latitude;
if (p.longitude < minLng) minLng = p.longitude;
if (p.longitude > maxLng) maxLng = p.longitude;
}
final bounds = LatLngBounds(
southwest: LatLng(minLat, minLng),
northeast: LatLng(maxLat, maxLng),
);
mapController?.animateCamera(
CameraUpdate.newLatLngBounds(
bounds,
left: 40.0,
top: 60.0,
right: 40.0,
bottom: 120.0,
),
);
}
/// Get Mode Title / الحصول على عنوان النمط الميداني الحالي
String getModeTitle() {
switch (currentTacticalMode.value) {
case 'los':
return 'تبادل الرؤية والمراقبة (LOS)';
case 'viewshed_360':
return 'رادار الرصد الدائري 360°';
case 'artillery':
return 'رماية المدفعية وقوس القذيفة';
case 'hlz':
return 'مهابط المروحيات (HLZ)';
case 'minefield':
return 'حقول الألغام وممرات العبور';
case 'isochrone':
return 'نطاق التدخل السريع (QRF)';
case 'symbols':
return 'الرموز والتشكيلات العسكرية';
case 'overlays':
return 'منظومة الشفافات (IPB)';
case 'routing':
return 'توجيه القوافل التكتيكي';
default:
return 'منظومة العمليات الميدانية (Off-Grid)';
}
}
/// Build all declarative map markers / تجميع كافة علامات الخريطة
Set<Marker> buildMarkers(ActiveNavigationState navState) {
final markers = <Marker>{};
// 1. Landmark & Observation Markers
for (final obs in resection.observations) {
markers.add(
Marker(
markerId: MarkerId(obs.landmark.id),
position: LatLng(obs.landmark.lat, obs.landmark.lng),
icon: InlqBitmap.defaultMarkerWithHue(InlqBitmap.hueAzure),
infoWindow: InfoWindow(
title: obs.landmark.name,
snippet: AngleFormatter.format(obs.trueAzimuthDeg, angleUnit.value),
),
),
);
}
// 2. Resection Fix Marker
if (resection.resectionResult.value != null && !navState.isNavigating) {
final res = resection.resectionResult.value!;
markers.add(
Marker(
markerId: const MarkerId('observer_calculated_position'),
position: LatLng(res.lat, res.lng),
icon: InlqBitmap.defaultMarkerWithHue(InlqBitmap.hueGreen),
infoWindow: const InfoWindow(
title: 'موقع الراصد المحسوب (GPS-Denied Fix)',
snippet: 'تم استخراج الموقع بالتقاطع البصري العكسي',
),
),
);
}
// 3. Routing Destination Marker
if (navigation.activeRoute.value != null &&
navigation.activeRoute.value!.polylinePoints.isNotEmpty) {
markers.add(
Marker(
markerId: const MarkerId('convoy_destination'),
position: navigation.activeRoute.value!.polylinePoints.last,
icon: InlqBitmap.defaultMarkerWithHue(InlqBitmap.hueRed),
infoWindow: const InfoWindow(
title: 'الهدف التكتيكي (Objective)',
snippet: 'نقطة الوصول المحددة',
),
),
);
}
// 4. Moving Vehicle Marker
if (navState.isNavigating && navState.currentPosition != null) {
markers.add(
Marker(
markerId: const MarkerId('active_vehicle_position'),
position: navState.currentPosition!,
icon: InlqBitmap.defaultMarkerWithHue(InlqBitmap.hueCyan),
infoWindow: InfoWindow(
title: 'مركبة العمليات الميدانية',
snippet:
'${navState.currentSpeedKmH.round()} كم/س • سمت ${navState.currentHeadingDeg.round()}°',
),
),
);
}
// 5. Tactical Placed Symbols
for (final sym in symbols.placedSymbols) {
markers.add(
Marker(
markerId: MarkerId(sym.id),
position: sym.position,
icon: InlqBitmap.defaultMarkerWithHue(
sym.isEnemy ? InlqBitmap.hueRed : InlqBitmap.hueBlue,
),
infoWindow: InfoWindow(
title: sym.name,
snippet: MilitaryGridUtils.latLngToMgrs(
sym.position.latitude, sym.position.longitude),
),
),
);
}
// 6. Artillery Gun & Target Markers
if (currentTacticalMode.value == 'artillery') {
if (artillery.gunPosition.value != null) {
markers.add(
Marker(
markerId: const MarkerId('artillery_gun_marker'),
position: artillery.gunPosition.value!,
icon: InlqBitmap.defaultMarkerWithHue(InlqBitmap.hueBlue),
infoWindow: const InfoWindow(title: '🎯 مربض المدفعية'),
),
);
}
if (artillery.targetPosition.value != null) {
markers.add(
Marker(
markerId: const MarkerId('artillery_target_marker'),
position: artillery.targetPosition.value!,
icon: InlqBitmap.defaultMarkerWithHue(InlqBitmap.hueRed),
infoWindow: const InfoWindow(title: '💥 الهدف المعادي'),
),
);
}
}
// 7. HLZ Center Marker
if (currentTacticalMode.value == 'hlz' && hlz.selectedPosition.value != null) {
markers.add(
Marker(
markerId: const MarkerId('hlz_center_marker'),
position: hlz.selectedPosition.value!,
icon: InlqBitmap.defaultMarkerWithHue(InlqBitmap.hueGreen),
infoWindow: const InfoWindow(title: '🚁 مهبط الطيران العامودي (HLZ)'),
),
);
}
return markers;
}
/// Build all declarative map polylines / تجميع مسارات الخريطة
Set<Polyline> buildPolylines(ActiveNavigationState navState) {
final polylines = <Polyline>{};
// 1. Tactical Line of Sight (LOS)
if (currentTacticalMode.value == 'los' &&
los.observerPosition.value != null &&
los.targetPosition.value != null &&
los.losReport.value != null &&
los.losReport.value!.profile.length > 1) {
final visSegments = los.losReport.value!.visibleSegments;
for (int i = 0; i < visSegments.length; i++) {
polylines.add(
Polyline(
polylineId: PolylineId('tactical_los_vis_$i'),
points: visSegments[i],
color: const Color(0xFF22C55E),
width: 5.0,
),
);
}
final blkSegments = los.losReport.value!.blockedSegments;
for (int i = 0; i < blkSegments.length; i++) {
polylines.add(
Polyline(
polylineId: PolylineId('tactical_los_blk_$i'),
points: blkSegments[i],
color: const Color(0xFFEF4444),
width: 5.0,
),
);
}
}
// 2. Artillery Ballistic Arc Ground Track
if (currentTacticalMode.value == 'artillery' &&
artillery.firingSolution.value != null) {
final points = artillery.firingSolution.value!.trajectoryProfile
.map((p) => p.coordinate)
.toList();
polylines.add(
Polyline(
polylineId: const PolylineId('artillery_trajectory_track'),
points: points,
color: artillery.firingSolution.value!.isCrestClear
? const Color(0xFF00F0FF)
: const Color(0xFFEF4444),
width: 4.5,
),
);
}
// 3. Minefield Safe Breaching Lane Centerline
if (currentTacticalMode.value == 'minefield' &&
minefield.zoneResult.value != null) {
polylines.add(
Polyline(
polylineId: const PolylineId('minefield_breach_centerline'),
points: minefield.zoneResult.value!.breachLaneCenterline,
color: const Color(0xFF10B981),
width: 4.0,
),
);
}
// 4. Tactical Overlays Lines
for (final layer in overlays.overlayLayers) {
if (layer.isVisible) {
for (int i = 0; i < layer.lines.length; i++) {
polylines.add(
Polyline(
polylineId: PolylineId('overlay_${layer.id}_line_$i'),
points: layer.lines[i],
color: layer.color,
width: 4.0,
),
);
}
}
}
// 5. Active Route Polyline
if (navigation.activeRoute.value != null &&
navigation.activeRoute.value!.polylinePoints.isNotEmpty) {
polylines.add(
Polyline(
polylineId: const PolylineId('tactical_on_device_route'),
points: navigation.activeRoute.value!.polylinePoints,
color: navState.isNavigating
? const Color(0xFF00F0FF)
: const Color(0xFF38BDF8),
width: navState.isNavigating ? 6.5 : 5.0,
),
);
}
return polylines;
}
/// Build all declarative map polygons / تجميع مضلعات الخريطة
Set<Polygon> buildPolygons() {
final polygons = <Polygon>{};
// 1. 360 Viewshed Radar Visible Polygon Fill
if (currentTacticalMode.value == 'viewshed_360' &&
viewshed.viewshedReport.value != null &&
viewshed.viewshedReport.value!.polygonVertices.isNotEmpty) {
polygons.add(
Polygon(
polygonId: const PolygonId('viewshed_360_fill'),
points: viewshed.viewshedReport.value!.polygonVertices,
fillColor: const Color(0x4422C55E),
strokeColor: const Color(0xFF22C55E),
strokeWidth: 2,
),
);
}
// 2. HLZ Pad Boundary & Approach Corridor
if (currentTacticalMode.value == 'hlz' &&
hlz.assessmentResult.value != null) {
polygons.add(
Polygon(
polygonId: const PolygonId('hlz_pad_boundary'),
points: hlz.assessmentResult.value!.padBoundary,
fillColor: hlz.assessmentResult.value!.gradeColor.withAlpha(50),
strokeColor: hlz.assessmentResult.value!.gradeColor,
strokeWidth: 2,
),
);
polygons.add(
Polygon(
polygonId: const PolygonId('hlz_approach_corridor'),
points: hlz.assessmentResult.value!.approachFunnel,
fillColor: const Color(0x3338BDF8),
strokeColor: const Color(0xFF38BDF8),
strokeWidth: 1.5,
),
);
}
// 3. Minefield Boundary & Safe Breaching Lane
if (currentTacticalMode.value == 'minefield' &&
minefield.zoneResult.value != null) {
polygons.add(
Polygon(
polygonId: const PolygonId('minefield_threat_boundary'),
points: minefield.zoneResult.value!.boundaryPolygon,
fillColor: const Color(0x44EF4444),
strokeColor: const Color(0xFFEF4444),
strokeWidth: 2,
),
);
polygons.add(
Polygon(
polygonId: const PolygonId('minefield_safe_breach_polygon'),
points: minefield.zoneResult.value!.breachLanePolygon,
fillColor: const Color(0x6610B981),
strokeColor: const Color(0xFF10B981),
strokeWidth: 2,
),
);
}
// 4. Isochrone Response Time Rings
if (currentTacticalMode.value == 'isochrone' &&
isochrone.isochroneRings.isNotEmpty) {
for (int i = 0; i < isochrone.isochroneRings.length; i++) {
final ring = isochrone.isochroneRings[i];
polygons.add(
Polygon(
polygonId: PolygonId('isochrone_ring_$i'),
points: ring.polygonCoordinates,
fillColor: ring.ringColor.withAlpha(30),
strokeColor: ring.ringColor,
strokeWidth: 2,
),
);
}
}
// 5. Tactical Overlays Polygons
for (final layer in overlays.overlayLayers) {
if (layer.isVisible) {
for (int i = 0; i < layer.polygons.length; i++) {
polygons.add(
Polygon(
polygonId: PolygonId('overlay_${layer.id}_poly_$i'),
points: layer.polygons[i],
fillColor: layer.color.withAlpha(40),
strokeColor: layer.color,
strokeWidth: 2,
),
);
}
}
}
return polygons;
}
}
@@ -0,0 +1,70 @@
import 'package:get/get.dart';
import 'package:intaleq_maps/intaleq_maps.dart';
import '../services/viewshed_360_engine.dart';
/// ============================================================================
/// [ViewshedController] - وحدة التحكم برادار الرصد الدائري 360°
/// ============================================================================
/// English:
/// GetX Controller managing 360° radial viewshed calculations, observer radar
/// radius, visible coverage polygons, and dead ground area metrics.
///
/// العربية:
/// متحكم GetX لإدارة رادار الرصد الدائري 360 درجة، وحساب مضلع التغطية البصرية
/// الفعلي ومساحة الرصد بالكيلومتر المربع مع تضاريس الأردن الحقيقية.
/// ============================================================================
class ViewshedController extends GetxController {
// ── Observables / الحالات التفاعلية ──────────────────────────────────────
final Rx<LatLng?> observerPosition = Rx<LatLng?>(null);
final RxDouble radiusKm = 5.0.obs;
final RxDouble observerHeightM = 2.0.obs;
final RxBool isCalculating = false.obs;
final Rx<Viewshed360Report?> viewshedReport = Rx<Viewshed360Report?>(null);
/// Set Viewshed Radar Center / تعيين مركز الرادار والرصد
void setObserver(LatLng pos) {
observerPosition.value = pos;
computeViewshed();
}
/// Set Radar Range Radius in KM / ضبط نصف قطر دائرة الرصد بالكيلومتر
void setRadius(double km) {
radiusKm.value = km;
computeViewshed();
}
/// Set Observer Antenna/Tower Height / ضبط ارتفاع سارية الرصد بالمتر
void setHeight(double heightM) {
observerHeightM.value = heightM;
computeViewshed();
}
/// Compute 360 Viewshed / تنفيذ الحساب التفاعلي لحقل الرؤية الدائري
Future<void> computeViewshed() async {
if (observerPosition.value == null) {
viewshedReport.value = null;
return;
}
try {
isCalculating.value = true;
final rep = await Viewshed360Engine.calculateAsync(
observer: observerPosition.value!,
radiusMeters: radiusKm.value * 1000.0,
observerHeightM: observerHeightM.value,
rayCount: 360,
);
viewshedReport.value = rep;
} catch (e) {
viewshedReport.value = null;
} finally {
isCalculating.value = false;
}
}
/// Reset viewshed state / إعادة ضبط الرادار الدائري
void reset() {
observerPosition.value = null;
viewshedReport.value = null;
}
}
+16 -1
View File
@@ -1,9 +1,22 @@
import 'package:flutter/material.dart';
import 'package:flutter_localizations/flutter_localizations.dart';
import 'package:get/get.dart';
import 'bindings/tactical_binding.dart';
import 'screens/tactical_map_screen.dart';
import 'services/landmark_database.dart';
import 'services/offline_routing_engine.dart';
/// ============================================================================
/// [main] - المدخل الرئيسي لتطبيق العمليات التكتيكية الميدانية (Intaleq Defense)
/// ============================================================================
/// English:
/// Application entry point initializing local SQLite cache, topological routing
/// graphs, GetX State Management, and tactical dark sovereign UI themes.
///
/// العربية:
/// نقطة انطلاق التطبيق، تقوم بتهيئة قواعد بيانات المعالم المحلية، محرك التوجيه،
/// حقن متحكمات GetX وتفعيل الثيم العسكري الليلي مع دعم اتجاه الكتابة من اليمين (RTL).
/// ============================================================================
void main() async {
WidgetsFlutterBinding.ensureInitialized();
await LandmarkDatabase.initCache();
@@ -16,10 +29,12 @@ class TacticalApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
return GetMaterialApp(
title: 'منظومة الخرائط التكتيكية والملاحة الميدانية - Intaleq Tactical Navigation',
debugShowCheckedModeBanner: false,
initialBinding: TacticalBinding(),
locale: const Locale('ar', 'JO'),
fallbackLocale: const Locale('en', 'US'),
supportedLocales: const [
Locale('ar', 'JO'),
Locale('en', 'US'),
File diff suppressed because it is too large Load Diff
+8
View File
@@ -373,6 +373,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "0.2.5"
get:
dependency: "direct main"
description:
name: get
sha256: "5ed34a7925b85336e15d472cc4cfe7d9ebf4ab8e8b9f688585bf6b50f4c3d79a"
url: "https://pub.dev"
source: hosted
version: "4.7.3"
glob:
dependency: transitive
description:
+1
View File
@@ -27,6 +27,7 @@ dependencies:
envied: ^1.1.1
sqflite: ^2.3.3+1
sqflite_common_ffi: ^2.3.3
get: ^4.6.6
dev_dependencies:
flutter_test: