import 'package:flutter/material.dart'; import 'package:get/get.dart'; import 'package:intaleq_maps/intaleq_maps.dart'; import '../config/app_config.dart'; import '../controllers/tactical_map_controller.dart'; import '../models/navigation_state.dart'; import '../services/turn_by_turn_navigation_engine.dart'; import '../widgets/active_navigation_hud.dart'; import '../widgets/camera_resection_view.dart'; import '../widgets/interactive_map_picker_hud.dart'; import '../widgets/optical_rangefinder_hud.dart'; import '../widgets/place_search_sheet.dart'; import '../widgets/tactical_artillery_sheet.dart'; import '../widgets/tactical_drawer.dart'; import '../widgets/tactical_hlz_sheet.dart'; import '../widgets/tactical_initial_provisioning_dialog.dart'; import '../widgets/tactical_isochrone_sheet.dart'; import '../widgets/tactical_los_sheet.dart'; import '../widgets/tactical_minefield_sheet.dart'; import '../widgets/tactical_overlays_sheet.dart'; import '../widgets/tactical_route_planner_sheet.dart'; import '../widgets/tactical_route_preview_card.dart'; import '../widgets/tactical_symbols_sheet.dart'; import '../widgets/tactical_viewshed_sheet.dart'; import '../models/tactical_sheet_type.dart'; import '../widgets/persistent_tactical_sheet_wrapper.dart'; /// ============================================================================ /// [TacticalMapScreen] - الشاشة التكتيكية الرئيسية (GetView Architecture) /// ============================================================================ /// English: /// Master map screen implemented as a reactive GetView listening to [TacticalMapController]. /// Decoupled from direct state mutations, using Obx for declarative UI rendering. /// /// العربية: /// شاشة العمليات والخريطة الرئيسية بنمط GetView التفاعلي، ترتبط بمتحكم [TacticalMapController] /// وتستخدم Obx لتحديث طبقات الخريطة واللوحات المنبثقة فور تغير الحالة الميدانية. /// ============================================================================ class TacticalMapScreen extends GetView { const TacticalMapScreen({super.key}); @override Widget build(BuildContext context) { final GlobalKey scaffoldKey = GlobalKey(); // Check Initial Provisioning on startup _checkProvisioning(context); return ValueListenableBuilder( valueListenable: TurnByTurnNavigationEngine.navigationState, builder: (context, navState, _) { return Scaffold( key: scaffoldKey, drawer: Obx( () => TacticalDrawer( currentAngleUnit: controller.angleUnit.value, onAngleUnitChanged: controller.setAngleUnit, onOpenResectionHud: () => controller.switchMode('resection_cam'), onStartRangefinderMode: () => controller.switchMode('rangefinder'), onStartRoutingMode: () => _openRoutePlanner(context), onStartLosMode: () { controller.switchMode('los'); _openLosSheet(context); }, onStartViewshedMode: () { controller.switchMode('viewshed_360'); _openViewshed360Sheet(context); }, onStartArtilleryMode: () { controller.switchMode('artillery'); _openArtillerySheet(context); }, onStartHlzMode: () { controller.switchMode('hlz'); _openHlzSheet(context); }, onStartMinefieldMode: () { controller.switchMode('minefield'); _openMinefieldSheet(context); }, onStartIsochroneMode: () { controller.switchMode('isochrone'); _openIsochroneSheet(context); }, onStartSymbolsMode: () { controller.switchMode('symbols'); _openSymbolsSheet(context); }, onStartOverlaysMode: () { controller.switchMode('overlays'); _openOverlaysSheet(context); }, onLandmarksSynced: () => controller.update(), onClearMap: () { controller.clearAllTacticalLayers(); ScaffoldMessenger.of(context).showSnackBar( const SnackBar( backgroundColor: Color(0xFF0F172A), content: Text('🧹 تم مسح كافة الرسوم والأدوات وتصفير الخريطة بنجاح.'), duration: Duration(seconds: 2), ), ); }, showContours: controller.showContours.value, onToggleContours: controller.toggleContours, ), ), body: Stack( children: [ // ── 1. Map Canvas with Declarative Layer Overlays ────── Obx(() { final markers = controller.buildMarkers(navState); final polylines = controller.buildPolylines(navState); final polygons = controller.buildPolygons(); return IntaleqMap( apiKey: AppConfig.apiKey, initialCameraPosition: CameraPosition( target: controller.currentGpsPosition.value ?? const LatLng(31.9539, 35.9106), zoom: 14.0, ), styleUrl: controller.showContours.value ? 'assets/tactical-style-contours.json' : 'assets/tactical-style.json', onMapCreated: (ctrl) { controller.mapController = ctrl; }, onStyleLoaded: () { if (controller.currentGpsPosition.value != null) { controller.mapController?.animateCamera( CameraUpdate.newLatLngZoom(controller.currentGpsPosition.value!, 14.0), ); } }, onCameraMove: (pos) { controller.currentCameraCenter.value = pos.target; }, onTap: controller.handleMapTap, markers: markers, polylines: polylines, polygons: polygons, ); }), // ── 2. Top Operations Floating Bar ──────────────────── if (!navState.isNavigating) Positioned( top: 48, left: 16, right: 16, child: Row( children: [ Container( decoration: BoxDecoration( color: const Color(0xFF090E17).withAlpha(220), borderRadius: BorderRadius.circular(12), border: Border.all( color: const Color(0xFF0071E3).withAlpha(120)), ), child: IconButton( icon: const Icon(Icons.menu, color: Color(0xFF38BDF8)), onPressed: () => scaffoldKey.currentState?.openDrawer(), ), ), const SizedBox(width: 8), Expanded( child: Container( height: 48, padding: const EdgeInsets.symmetric(horizontal: 14), decoration: BoxDecoration( color: const Color(0xFF090E17).withAlpha(220), borderRadius: BorderRadius.circular(12), border: Border.all(color: Colors.white12), ), child: Row( children: [ const Icon(Icons.shield, color: Color(0xFF0071E3), size: 20), const SizedBox(width: 8), Expanded( child: Obx( () => Text( controller.getModeTitle(), style: const TextStyle( color: Colors.white, fontSize: 12.5, fontWeight: FontWeight.bold, ), overflow: TextOverflow.ellipsis, maxLines: 1, ), ), ), ], ), ), ), const SizedBox(width: 8), Container( decoration: BoxDecoration( color: const Color(0xFF090E17).withAlpha(220), borderRadius: BorderRadius.circular(12), border: Border.all(color: Colors.white12), ), child: IconButton( icon: const Icon(Icons.cleaning_services_rounded, color: Color(0xFFF43F5E), size: 20), tooltip: 'مسح وتنظيف الخريطة', onPressed: () { controller.clearAllTacticalLayers(); ScaffoldMessenger.of(context).showSnackBar( const SnackBar( backgroundColor: Color(0xFF0F172A), content: Text('🧹 تم مسح كافة الرسوم والأدوات وتصفير الخريطة بنجاح.'), duration: Duration(seconds: 2), ), ); }, ), ), const SizedBox(width: 8), Container( decoration: BoxDecoration( color: const Color(0xFF090E17).withAlpha(220), borderRadius: BorderRadius.circular(12), border: Border.all(color: Colors.white12), ), child: IconButton( icon: const Icon(Icons.search, color: Color(0xFF38BDF8)), onPressed: () => _openPlaceSearch(context), ), ), ], ), ), // ── 3. Interactive Crosshair Target Picker HUD ───────── Obx(() { if (controller.activePickerTarget.value == null) { return const SizedBox.shrink(); } return InteractiveMapPickerHud( target: controller.activePickerTarget.value!, centerPosition: controller.currentCameraCenter.value, onConfirm: () => controller.confirmPicker( controller.activePickerTarget.value!, controller.currentCameraCenter.value, ), onCancel: controller.cancelPicker, ); }), // ── 4. Optical Resection Camera HUD (GPS-Denied) ─────── Obx(() { if (controller.currentTacticalMode.value != 'resection_cam') { return const SizedBox.shrink(); } return CameraResectionView( observations: controller.resection.observations, angleUnit: controller.angleUnit.value, initialSelectedLandmark: controller.resection.selectedMapLandmark.value, onObservationAdded: controller.resection.addObservation, onPickFromMap: () { controller.switchMode('nav'); controller.setPickerTarget(MapPickerTarget.resectionLandmark); }, onCalculatePressed: () { final res = controller.resection.computePosition(); if (res != null) { controller.switchMode('nav'); controller.mapController?.animateCamera( CameraUpdate.newLatLngZoom( LatLng(res.lat, res.lng), 15.0), ); } }, onResetPressed: controller.resection.reset, onClose: () => controller.switchMode('nav'), ); }), // ── 4b. Optical Rangefinder Camera HUD (قياس المدى البصري) ─── Obx(() { if (controller.currentTacticalMode.value != 'rangefinder') { return const SizedBox.shrink(); } return OpticalRangefinderHud( angleUnit: controller.angleUnit.value, onClose: () => controller.switchMode('nav'), onRangeLocked: (dist, heading, pitch) { controller.switchMode('nav'); }, ); }), // ── 4c. Persistent Tactical Sheet ────────────────────────── Positioned( bottom: 0, left: 0, right: 0, child: Obx(() { final activeSheet = controller.activeSheet.value; if (activeSheet == null || navState.isNavigating) { return const SizedBox.shrink(); } Widget sheetContent = const SizedBox.shrink(); switch (activeSheet.type) { case TacticalSheetType.route: sheetContent = TacticalRoutePlannerSheet( initialOrigin: controller.pickedRouteOrigin.value, initialDestination: controller.pickedRouteDestination.value, currentGps: controller.currentGpsPosition.value ?? const LatLng(31.9539, 35.9106), resectionFix: controller.resection.resectionResult.value != null ? LatLng(controller.resection.resectionResult.value!.lat, controller.resection.resectionResult.value!.lng) : null, onRouteConfirmed: (origin, destination, profile, autoStartNav) async { final plan = await controller.navigation.calculateRoute( origin: origin, destination: destination, profile: profile, autoStart: autoStartNav, mapController: controller.mapController, ); if (plan != null && plan.polylinePoints.isNotEmpty) { controller.fitBounds(plan.polylinePoints); } }, onPickOriginOnMap: () { controller.isSheetMinimized.value = true; controller.setPickerTarget(MapPickerTarget.routeOrigin); }, onPickDestinationOnMap: () { controller.isSheetMinimized.value = true; controller.setPickerTarget(MapPickerTarget.routeDestination); }, onClose: controller.closeSheet, ); break; case TacticalSheetType.los: sheetContent = TacticalLosSheet( observer: controller.los.observerPosition.value ?? const LatLng(31.9539, 35.9106), target: controller.los.targetPosition.value ?? const LatLng(31.9800, 35.9500), currentGps: controller.currentGpsPosition.value, angleUnit: controller.angleUnit.value, onUpdateLocations: (newObs, newTgt) { controller.los.setObserver(newObs); controller.los.setTarget(newTgt); controller.fitBounds([newObs, newTgt]); }, onVisibilityChanged: (_) {}, onReportGenerated: (_) {}, onPickObserverOnMap: () { controller.isSheetMinimized.value = true; controller.setPickerTarget(MapPickerTarget.losObserver); }, onPickTargetOnMap: () { controller.isSheetMinimized.value = true; controller.setPickerTarget(MapPickerTarget.losTarget); }, onClose: () { controller.closeSheet(); controller.switchMode('nav'); }, ); break; case TacticalSheetType.viewshed: sheetContent = TacticalViewshedSheet( observer: controller.viewshed.observerPosition.value ?? controller.currentGpsPosition.value ?? const LatLng(31.9539, 35.9106), currentGps: controller.currentGpsPosition.value, onUpdateObserver: controller.viewshed.setObserver, onReportUpdated: (_) {}, onPickObserverOnMap: () { controller.isSheetMinimized.value = true; controller.setPickerTarget(MapPickerTarget.viewshedCenter); }, onClose: controller.closeSheet, ); break; case TacticalSheetType.artillery: sheetContent = TacticalArtillerySheet( gunPosition: controller.artillery.gunPosition.value ?? controller.currentGpsPosition.value ?? const LatLng(31.9539, 35.9106), targetPosition: controller.artillery.targetPosition.value, angleUnit: controller.angleUnit.value, onPickGun: () { controller.isSheetMinimized.value = true; controller.setPickerTarget(MapPickerTarget.artilleryGun); }, onPickTarget: () { controller.isSheetMinimized.value = true; controller.setPickerTarget(MapPickerTarget.artilleryTarget); }, onSwap: controller.artillery.swapPositions, onSolutionCalculated: (sol) { controller.fitBounds([sol.gunPosition, sol.targetPosition]); }, onClose: controller.closeSheet, ); break; case TacticalSheetType.hlz: sheetContent = TacticalHlzSheet( selectedPosition: controller.hlz.selectedPosition.value ?? controller.currentGpsPosition.value ?? const LatLng(31.9539, 35.9106), onPickLocation: () { controller.isSheetMinimized.value = true; controller.setPickerTarget(MapPickerTarget.hlzCenter); }, onAssessmentCompleted: (_) {}, onClose: controller.closeSheet, ); break; case TacticalSheetType.minefield: sheetContent = TacticalMinefieldSheet( startPoint: controller.minefield.startPoint.value ?? controller.currentGpsPosition.value ?? const LatLng(31.9539, 35.9106), endPoint: controller.minefield.endPoint.value, onPickStart: () { controller.isSheetMinimized.value = true; controller.setPickerTarget(MapPickerTarget.minefieldStart); }, onPickEnd: () { controller.isSheetMinimized.value = true; controller.setPickerTarget(MapPickerTarget.minefieldEnd); }, onSwap: controller.minefield.swapPoints, onZoneCalculated: (res) { controller.fitBounds([res.startPoint, res.endPoint]); }, onClose: controller.closeSheet, ); break; case TacticalSheetType.isochrone: sheetContent = TacticalIsochroneSheet( center: controller.isochrone.center.value ?? controller.currentGpsPosition.value ?? const LatLng(31.9539, 35.9106), onPickCenter: () { controller.isSheetMinimized.value = true; controller.setPickerTarget(MapPickerTarget.isochroneCenter); }, onIsochronesCalculated: (_) {}, onClose: controller.closeSheet, ); break; case TacticalSheetType.symbols: sheetContent = TacticalSymbolsSheet( activePlacementType: controller.symbols.activePlacementType.value, placedSymbols: controller.symbols.placedSymbols, onSelectSymbolType: (type) { controller.symbols.selectSymbolForPlacement(type); controller.isSheetMinimized.value = true; Get.snackbar( 'الرموز العسكرية', 'انقر على الخريطة لتثبيت الرمز في الميدان 📍', backgroundColor: const Color(0xFF0071E3), colorText: Colors.white, snackPosition: SnackPosition.BOTTOM, ); }, onDeleteSymbol: (s) => controller.symbols.removeSymbol(s.id), onClose: controller.closeSheet, ); break; case TacticalSheetType.overlays: sheetContent = TacticalOverlaysSheet( layers: controller.overlays.overlayLayers, onToggleLayer: controller.overlays.toggleLayer, onClose: controller.closeSheet, ); break; } return PersistentTacticalSheetWrapper( title: activeSheet.title, icon: activeSheet.icon, isMinimized: controller.isSheetMinimized.value, onToggleMinimize: () => controller.isSheetMinimized.toggle(), onClose: controller.closeSheet, child: sheetContent, ); }), ), // ── 5. Active Turn-by-Turn Navigation HUD ────────────── if (navState.isNavigating) ...[ Positioned( top: 48, left: 16, right: 16, child: ActiveNavigationTopBanner(navState: navState), ), Positioned( bottom: 20, left: 16, right: 16, child: ActiveNavigationBottomHUD( navState: navState, onStopNavigation: controller.navigation.stopNavigation, ), ), ], // ── 6. Tactical Route Preview Card ──────────────────── // Positioned خارج Obx إلزامياً: Positioned يجب أن يكون ابناً // مباشراً لـ Stack وإلا فشل التخطيط وhit-test. Positioned( bottom: 16, left: 16, right: 16, child: Obx(() { final route = controller.navigation.activeRoute.value; if (route == null || navState.isNavigating) { return const SizedBox.shrink(); } return TacticalRoutePreviewCard( activeRoute: route, onClose: () => controller.navigation.reset(), onStartNavigation: () { controller.navigation.startNavigation( mapController: controller.mapController, ); }, ); }), ), ], ), ); }, ); } // ── Operations Modal Launchers ───────────────────────────── Future _checkProvisioning(BuildContext context) async { final needed = await TacticalInitialProvisioningDialog.isProvisioningNeeded(); if (needed) { WidgetsBinding.instance.addPostFrameCallback((_) { showDialog( context: context, barrierDismissible: false, builder: (ctx) => TacticalInitialProvisioningDialog( onCompleted: () => Navigator.pop(ctx), ), ); }); } } void _openRoutePlanner(BuildContext context) { controller.openSheet(ActiveTacticalSheet(TacticalSheetType.route, 'مخطط مسار القوافل', Icons.route)); } void _openLosSheet(BuildContext context) { controller.openSheet(ActiveTacticalSheet(TacticalSheetType.los, 'تحليل خط الرؤية (LOS)', Icons.visibility)); } void _openViewshed360Sheet(BuildContext context) { controller.openSheet(ActiveTacticalSheet(TacticalSheetType.viewshed, 'رادار الرؤية 360 درجة', Icons.radar)); } void _openArtillerySheet(BuildContext context) { controller.openSheet(ActiveTacticalSheet(TacticalSheetType.artillery, 'حاسبة المدفعية الميدانية', Icons.track_changes)); } void _openHlzSheet(BuildContext context) { controller.openSheet(ActiveTacticalSheet(TacticalSheetType.hlz, 'استطلاع المهابط (HLZ)', Icons.local_airport)); } void _openMinefieldSheet(BuildContext context) { controller.openSheet(ActiveTacticalSheet(TacticalSheetType.minefield, 'تأمين الممرات والألغام', Icons.warning_amber_rounded)); } void _openIsochroneSheet(BuildContext context) { controller.openSheet(ActiveTacticalSheet(TacticalSheetType.isochrone, 'نطاقات الحركة والإمداد', Icons.speed)); } void _openSymbolsSheet(BuildContext context) { controller.openSheet(ActiveTacticalSheet(TacticalSheetType.symbols, 'الرموز العسكرية (MIL-STD)', Icons.category)); } void _openOverlaysSheet(BuildContext context) { controller.openSheet(ActiveTacticalSheet(TacticalSheetType.overlays, 'شفافات العمليات (IPB)', Icons.layers)); } void _openPlaceSearch(BuildContext context) { showModalBottomSheet( context: context, isScrollControlled: true, backgroundColor: Colors.transparent, builder: (ctx) => PlaceSearchSheet( onDestinationSelected: (landmark, startNav) { final dest = LatLng(landmark.lat, landmark.lng); final origin = controller.currentGpsPosition.value ?? const LatLng(31.9539, 35.9106); controller.navigation.calculateRoute( origin: origin, destination: dest, autoStart: startNav, mapController: controller.mapController, ); }, ), ); } }