import re path = "packages/tactical_app/lib/screens/tactical_map_screen.dart" with open(path, "r") as f: content = f.read() # 1. Add imports imports = """ import '../models/tactical_sheet_type.dart'; import '../widgets/persistent_tactical_sheet_wrapper.dart'; """ content = content.replace("import '../widgets/tactical_viewshed_sheet.dart';", "import '../widgets/tactical_viewshed_sheet.dart';\n" + imports) # 2. Add Persistent Sheet in Stack # We will insert it right before the active navigation HUDs (around line 287) # or just after the InteractiveMapPickerHud (around line 242). # Let's find " // ── 5. Active Turn-by-Turn Navigation HUD ──────────────" stack_insert = """ // ── 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 ────────────── """ content = content.replace(" // ── 5. Active Turn-by-Turn Navigation HUD ──────────────", stack_insert) # 3. Replace the _openX methods to just open the sheet and not use showModalBottomSheet replacements = { "void _openRoutePlanner(BuildContext context) {": "void _openRoutePlanner(BuildContext context) {\n Navigator.pop(context);\n controller.openSheet(ActiveTacticalSheet(TacticalSheetType.route, 'مخطط مسار القوافل', Icons.route));\n }", "void _openLosSheet(BuildContext context) {": "void _openLosSheet(BuildContext context) {\n Navigator.pop(context);\n controller.openSheet(ActiveTacticalSheet(TacticalSheetType.los, 'تحليل خط الرؤية (LOS)', Icons.visibility));\n }", "void _openViewshed360Sheet(BuildContext context) {": "void _openViewshed360Sheet(BuildContext context) {\n Navigator.pop(context);\n controller.openSheet(ActiveTacticalSheet(TacticalSheetType.viewshed, 'رادار الرؤية 360 درجة', Icons.radar));\n }", "void _openArtillerySheet(BuildContext context) {": "void _openArtillerySheet(BuildContext context) {\n Navigator.pop(context);\n controller.openSheet(ActiveTacticalSheet(TacticalSheetType.artillery, 'حاسبة المدفعية الميدانية', Icons.track_changes));\n }", "void _openHlzSheet(BuildContext context) {": "void _openHlzSheet(BuildContext context) {\n Navigator.pop(context);\n controller.openSheet(ActiveTacticalSheet(TacticalSheetType.hlz, 'استطلاع المهابط (HLZ)', Icons.local_airport));\n }", "void _openMinefieldSheet(BuildContext context) {": "void _openMinefieldSheet(BuildContext context) {\n Navigator.pop(context);\n controller.openSheet(ActiveTacticalSheet(TacticalSheetType.minefield, 'تأمين الممرات والألغام', Icons.warning_amber_rounded));\n }", "void _openIsochroneSheet(BuildContext context) {": "void _openIsochroneSheet(BuildContext context) {\n Navigator.pop(context);\n controller.openSheet(ActiveTacticalSheet(TacticalSheetType.isochrone, 'نطاقات الحركة والإمداد', Icons.speed));\n }", "void _openSymbolsSheet(BuildContext context) {": "void _openSymbolsSheet(BuildContext context) {\n Navigator.pop(context);\n controller.openSheet(ActiveTacticalSheet(TacticalSheetType.symbols, 'الرموز العسكرية (MIL-STD)', Icons.category));\n }", "void _openOverlaysSheet(BuildContext context) {": "void _openOverlaysSheet(BuildContext context) {\n Navigator.pop(context);\n controller.openSheet(ActiveTacticalSheet(TacticalSheetType.overlays, 'شفافات العمليات (IPB)', Icons.layers));\n }" } # We need to remove the existing bodies of these methods. for func_start, new_func in replacements.items(): # Find the start of the function idx = content.find(func_start) if idx == -1: continue # Find the matching closing brace (very naive approach since it's at the end of the file) end_idx = idx + len(func_start) brace_count = 1 while brace_count > 0 and end_idx < len(content): if content[end_idx] == '{': brace_count += 1 elif content[end_idx] == '}': brace_count -= 1 end_idx += 1 # Replace content = content[:idx] + new_func + content[end_idx:] with open(path, "w") as f: f.write(content)