import 'package:flutter/material.dart'; import 'package:intaleq_maps/intaleq_maps.dart'; import '../services/viewshed_360_engine.dart'; import '../services/offline_los_engine.dart'; import '../services/military_grid_utils.dart'; /// Tactical Bottom Sheet for 360° Radial Viewshed (تحليل حقل الرؤية الدائري 360 درجة) class TacticalViewshedSheet extends StatefulWidget { final LatLng observer; final LatLng? currentGps; final Function(LatLng newObs)? onUpdateObserver; final Function(Viewshed360Report report) onReportUpdated; final VoidCallback onPickObserverOnMap; final VoidCallback onClose; const TacticalViewshedSheet({ super.key, required this.observer, this.currentGps, this.onUpdateObserver, required this.onReportUpdated, required this.onPickObserverOnMap, required this.onClose, }); @override State createState() => _TacticalViewshedSheetState(); } class _TacticalViewshedSheetState extends State with SingleTickerProviderStateMixin { late LatLng _currentObs; double _radiusKm = 5.0; double _observerHeightM = 2.0; final int _rayCount = 360; // 360 continuous 1-degree radial rays (15-20ms) Viewshed360Report? _report; bool _isCalculating = false; bool _showCoordInputs = false; late TabController _coordTabController; late TextEditingController _obsEastingCtrl; late TextEditingController _obsNorthingCtrl; late TextEditingController _obsLatCtrl; late TextEditingController _obsLngCtrl; @override void initState() { super.initState(); _currentObs = widget.observer; _coordTabController = TabController(length: 2, vsync: this); final obsMil = MilitaryGridUtils.fromLatLng(_currentObs); _obsEastingCtrl = TextEditingController(text: obsMil.eastingStr); _obsNorthingCtrl = TextEditingController(text: obsMil.northingStr); _obsLatCtrl = TextEditingController(text: _currentObs.latitude.toStringAsFixed(6)); _obsLngCtrl = TextEditingController(text: _currentObs.longitude.toStringAsFixed(6)); WidgetsBinding.instance.addPostFrameCallback((_) { if (mounted) _computeViewshed(); }); } @override void didUpdateWidget(covariant TacticalViewshedSheet oldWidget) { super.didUpdateWidget(oldWidget); if (oldWidget.observer != widget.observer) { _currentObs = widget.observer; final obsMil = MilitaryGridUtils.fromLatLng(_currentObs); _obsEastingCtrl.text = obsMil.eastingStr; _obsNorthingCtrl.text = obsMil.northingStr; _obsLatCtrl.text = _currentObs.latitude.toStringAsFixed(6); _obsLngCtrl.text = _currentObs.longitude.toStringAsFixed(6); WidgetsBinding.instance.addPostFrameCallback((_) { if (mounted) _computeViewshed(); }); } } @override void dispose() { _coordTabController.dispose(); _obsEastingCtrl.dispose(); _obsNorthingCtrl.dispose(); _obsLatCtrl.dispose(); _obsLngCtrl.dispose(); super.dispose(); } void _computeViewshed() async { setState(() => _isCalculating = true); final report = await Viewshed360Engine.calculateAsync( observer: _currentObs, radiusMeters: _radiusKm * 1000.0, observerHeightM: _observerHeightM, rayCount: _rayCount, ); if (mounted) { setState(() { _report = report; _isCalculating = false; }); widget.onReportUpdated(report); } } void _applyManualCoordinates() { LatLng newObs = _currentObs; if (_coordTabController.index == 0) { final e = double.tryParse(_obsEastingCtrl.text) ?? 0; final n = double.tryParse(_obsNorthingCtrl.text) ?? 0; if (e > 100000 && n > 1000000) { newObs = MilitaryGridUtils.toLatLng(easting: e, northing: n); } } else { final lat = double.tryParse(_obsLatCtrl.text); final lng = double.tryParse(_obsLngCtrl.text); if (lat != null && lng != null) { newObs = LatLng(lat, lng); } } setState(() { _currentObs = newObs; _showCoordInputs = false; }); widget.onUpdateObserver?.call(newObs); _computeViewshed(); } @override Widget build(BuildContext context) { final obsMil = MilitaryGridUtils.fromLatLng(_currentObs); final obsElev = JordanDemSurface.elevationAt(_currentObs.latitude, _currentObs.longitude); return Container( padding: const EdgeInsets.all(16), decoration: const BoxDecoration( color: Color(0xFF090E1A), borderRadius: BorderRadius.vertical(top: Radius.circular(24)), border: Border(top: BorderSide(color: Color(0xFF38BDF8), width: 1.5)), ), child: SafeArea( top: false, child: SingleChildScrollView( child: Column( mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.stretch, children: [ // Handle Bar Center( child: Container( width: 40, height: 4, margin: const EdgeInsets.only(bottom: 12), decoration: BoxDecoration( color: Colors.white24, borderRadius: BorderRadius.circular(2), ), ), ), // Header Bar Row( children: [ Container( padding: const EdgeInsets.all(8), decoration: BoxDecoration( color: const Color(0xFF22C55E).withValues(alpha: 0.15), borderRadius: BorderRadius.circular(10), border: Border.all(color: const Color(0xFF22C55E)), ), child: _isCalculating ? const SizedBox( width: 22, height: 22, child: CircularProgressIndicator(strokeWidth: 2, color: Color(0xFF4ADE80)), ) : const Icon(Icons.radar, color: Color(0xFF4ADE80), size: 22), ), const SizedBox(width: 12), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ const Text( 'منظومة حقل الرؤية والرصد 360° (360° Viewshed)', style: TextStyle(color: Colors.white, fontSize: 13, fontWeight: FontWeight.bold), overflow: TextOverflow.ellipsis, ), Text( 'مركز الرصد: ${obsMil.arabicFullFormat} • ارتفاع: ${obsElev.round()}م', style: const TextStyle(color: Color(0xFF38BDF8), fontSize: 11), overflow: TextOverflow.ellipsis, ), ], ), ), IconButton( onPressed: widget.onClose, icon: const Icon(Icons.close, color: Colors.white70, size: 20), ), ], ), const SizedBox(height: 12), // Metrics Card if (_report != null) Container( padding: const EdgeInsets.all(16), decoration: BoxDecoration( color: const Color(0xFF0F172A), borderRadius: BorderRadius.circular(12), border: Border.all(color: Colors.white10), ), child: Row( mainAxisAlignment: MainAxisAlignment.spaceAround, children: [ _buildMetricCol( 'المساحة المكشوفة', '${_report!.visibleAreaSqKm.toStringAsFixed(1)} كم²', const Color(0xFF4ADE80), ), _buildDivider(), _buildMetricCol( 'كفاءة التغطية', '${_report!.visibilityPercentage}%', _report!.visibilityPercentage > 50 ? const Color(0xFF4ADE80) : const Color(0xFFF59E0B), ), _buildDivider(), _buildMetricCol( 'متوسط المدى', '${_report!.averageSightRangeKm.toStringAsFixed(1)} كم', const Color(0xFF38BDF8), ), ], ), ), const SizedBox(height: 12), // Radius Slider Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Row( children: [ const Icon(Icons.blur_circular, size: 16, color: Color(0xFF38BDF8)), const SizedBox(width: 6), Text( 'نصف قطر المسح: ${_radiusKm.toStringAsFixed(1)} كم', style: const TextStyle(color: Colors.white, fontSize: 12, fontWeight: FontWeight.bold), ), ], ), Text( '${(_radiusKm * 1000).round()} متر', style: const TextStyle(color: Colors.white60, fontSize: 11), ), ], ), Slider( value: _radiusKm, min: 1.0, max: 15.0, divisions: 14, activeColor: const Color(0xFF38BDF8), inactiveColor: Colors.white12, onChanged: (val) { setState(() => _radiusKm = val); _computeViewshed(); }, ), // Optic Height Segmented Choice Row( children: [ const Icon(Icons.height, size: 16, color: Color(0xFFF59E0B)), const SizedBox(width: 6), const Text( 'ارتفاع نقطة الرصد / البصريات:', style: TextStyle(color: Colors.white, fontSize: 12, fontWeight: FontWeight.bold), ), ], ), const SizedBox(height: 6), Row( children: [ _buildHeightChip(2.0, '2م (منظار)'), const SizedBox(width: 6), _buildHeightChip(5.0, '5م (مركبة)'), const SizedBox(width: 6), _buildHeightChip(10.0, '10م (سارية)'), const SizedBox(width: 6), _buildHeightChip(25.0, '25م (برج)'), ], ), const SizedBox(height: 12), // Action Buttons Row: [Manual Coordinates / Map Selection / Current GPS] Row( children: [ Expanded( child: OutlinedButton.icon( onPressed: () => setState(() => _showCoordInputs = !_showCoordInputs), style: OutlinedButton.styleFrom( foregroundColor: const Color(0xFF38BDF8), side: const BorderSide(color: Color(0xFF38BDF8)), padding: const EdgeInsets.all(16), shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(10)), ), icon: Icon(_showCoordInputs ? Icons.expand_less : Icons.edit_location_alt, size: 16), label: const Text('إدخال إحداثيات', style: TextStyle(fontSize: 11, fontWeight: FontWeight.bold)), ), ), const SizedBox(width: 8), Expanded( child: ElevatedButton.icon( onPressed: widget.onPickObserverOnMap, style: ElevatedButton.styleFrom( backgroundColor: const Color(0xFF0284C7), foregroundColor: Colors.white, padding: const EdgeInsets.all(16), shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(10)), ), icon: const Icon(Icons.touch_app, size: 16), label: const Text('نقر من الخريطة 👁️', style: TextStyle(fontSize: 11, fontWeight: FontWeight.bold)), ), ), if (widget.currentGps != null) ...[ const SizedBox(width: 8), IconButton( tooltip: 'استخدام موقعي الحالي', style: IconButton.styleFrom( backgroundColor: const Color(0xFF1E293B), foregroundColor: const Color(0xFF22C55E), shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(10), side: const BorderSide(color: Color(0xFF22C55E)), ), ), icon: const Icon(Icons.my_location, size: 18), onPressed: () { setState(() { _currentObs = widget.currentGps!; }); widget.onUpdateObserver?.call(_currentObs); _computeViewshed(); }, ), ], ], ), // Coordinate Inputs Box if (_showCoordInputs) ...[ const SizedBox(height: 12), Container( padding: const EdgeInsets.all(12), decoration: BoxDecoration( color: const Color(0xFF1E293B), borderRadius: BorderRadius.circular(12), border: Border.all(color: Colors.white12), ), child: Column( crossAxisAlignment: CrossAxisAlignment.stretch, children: [ TabBar( controller: _coordTabController, indicatorColor: const Color(0xFF38BDF8), labelColor: const Color(0xFF38BDF8), unselectedLabelColor: Colors.white60, tabs: const [ Tab(text: 'شبكة عسكرية (UTM)'), Tab(text: 'درجات عشرية (Lat/Lng)'), ], ), const SizedBox(height: 10), SizedBox( height: 70, child: TabBarView( controller: _coordTabController, children: [ // UTM Row( children: [ Expanded( child: TextField( controller: _obsEastingCtrl, keyboardType: TextInputType.number, style: const TextStyle(color: Colors.white, fontSize: 12), decoration: InputDecoration( labelText: 'شرقيات (Easting)', labelStyle: const TextStyle(color: Colors.white60, fontSize: 11), filled: true, fillColor: const Color(0xFF0F172A), border: OutlineInputBorder(borderRadius: BorderRadius.circular(8)), ), ), ), const SizedBox(width: 8), Expanded( child: TextField( controller: _obsNorthingCtrl, keyboardType: TextInputType.number, style: const TextStyle(color: Colors.white, fontSize: 12), decoration: InputDecoration( labelText: 'شماليات (Northing)', labelStyle: const TextStyle(color: Colors.white60, fontSize: 11), filled: true, fillColor: const Color(0xFF0F172A), border: OutlineInputBorder(borderRadius: BorderRadius.circular(8)), ), ), ), ], ), // Decimal Row( children: [ Expanded( child: TextField( controller: _obsLatCtrl, keyboardType: const TextInputType.numberWithOptions(decimal: true), style: const TextStyle(color: Colors.white, fontSize: 12), decoration: InputDecoration( labelText: 'دائرة العرض (Lat)', labelStyle: const TextStyle(color: Colors.white60, fontSize: 11), filled: true, fillColor: const Color(0xFF0F172A), border: OutlineInputBorder(borderRadius: BorderRadius.circular(8)), ), ), ), const SizedBox(width: 8), Expanded( child: TextField( controller: _obsLngCtrl, keyboardType: const TextInputType.numberWithOptions(decimal: true), style: const TextStyle(color: Colors.white, fontSize: 12), decoration: InputDecoration( labelText: 'خط الطول (Lng)', labelStyle: const TextStyle(color: Colors.white60, fontSize: 11), filled: true, fillColor: const Color(0xFF0F172A), border: OutlineInputBorder(borderRadius: BorderRadius.circular(8)), ), ), ), ], ), ], ), ), const SizedBox(height: 8), ElevatedButton( onPressed: _applyManualCoordinates, style: ElevatedButton.styleFrom( backgroundColor: const Color(0xFF22C55E), foregroundColor: Colors.white, padding: const EdgeInsets.all(16), shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)), ), child: const Text('تطبيق الإحداثيات وإعادة الرصد', style: TextStyle(fontWeight: FontWeight.bold)), ), ], ), ), ], ], ), ), ), ); } Widget _buildHeightChip(double h, String label) { final isSelected = _observerHeightM == h; return Expanded( child: InkWell( onTap: () { setState(() => _observerHeightM = h); _computeViewshed(); }, child: Container( padding: const EdgeInsets.all(16), decoration: BoxDecoration( color: isSelected ? const Color(0xFF0284C7) : const Color(0xFF1E293B), borderRadius: BorderRadius.circular(8), border: Border.all(color: isSelected ? const Color(0xFF38BDF8) : Colors.white12), ), child: Center( child: Text( label, style: TextStyle( color: isSelected ? Colors.white : Colors.white70, fontSize: 9.5, fontWeight: isSelected ? FontWeight.bold : FontWeight.normal, ), ), ), ), ), ); } Widget _buildMetricCol(String title, String val, Color color) { return Column( children: [ Text(title, style: const TextStyle(color: Colors.white60, fontSize: 10)), const SizedBox(height: 2), Text(val, style: TextStyle(color: color, fontSize: 13, fontWeight: FontWeight.bold)), ], ); } Widget _buildDivider() { return Container(width: 1, height: 26, color: Colors.white12); } }