import 'package:camera/camera.dart'; import 'package:flutter/material.dart'; import 'package:flutter_compass/flutter_compass.dart'; import '../models/angle_unit.dart'; import '../models/landmark.dart'; import '../services/landmark_database.dart'; import '../services/resection_calculator.dart'; import 'tactical_crosshair.dart'; class CameraResectionView extends StatefulWidget { final List observations; final AngleUnit angleUnit; final Function(ResectionObservation) onObservationAdded; final VoidCallback onCalculatePressed; final VoidCallback onResetPressed; final VoidCallback onClose; const CameraResectionView({ super.key, required this.observations, required this.angleUnit, required this.onObservationAdded, required this.onCalculatePressed, required this.onResetPressed, required this.onClose, }); @override State createState() => _CameraResectionViewState(); } class _CameraResectionViewState extends State { CameraController? _cameraController; bool _isCameraReady = false; double _headingDeg = 45.0; bool _isLockedAnim = false; // Search & Filter state final TextEditingController _searchController = TextEditingController(); String _selectedRegion = 'الكل'; List _filteredLandmarks = []; TacticalLandmark? _selectedLandmark; final List _regions = const [ 'الكل', 'عمان الغربية', 'وسط عمان', 'شمال عمان', 'الزرقاء', 'إربد', 'عجلون', 'جرش', 'المفرق', 'الكرك', 'الطفيلة', 'معان', 'العقبة', ]; @override void initState() { super.initState(); _filteredLandmarks = LandmarkDatabase.allLandmarks; if (_filteredLandmarks.isNotEmpty) { _selectedLandmark = _filteredLandmarks[0]; } _initCamera(); _initCompass(); } Future _initCamera() async { try { final cameras = await availableCameras(); if (cameras.isNotEmpty) { final backCamera = cameras.firstWhere( (c) => c.lensDirection == CameraLensDirection.back, orElse: () => cameras.first, ); _cameraController = CameraController( backCamera, ResolutionPreset.high, enableAudio: false, ); await _cameraController!.initialize(); if (mounted) { setState(() => _isCameraReady = true); } } } catch (e) { debugPrint('Camera init exception: $e'); } } void _initCompass() { FlutterCompass.events?.listen((event) { if (event.heading != null && mounted) { setState(() { _headingDeg = (event.heading! + 360) % 360; }); } }); } @override void dispose() { _cameraController?.dispose(); _searchController.dispose(); super.dispose(); } void _onSearchChanged(String query) { setState(() { _filteredLandmarks = LandmarkDatabase.search( query, region: _selectedRegion, ); if (_filteredLandmarks.isNotEmpty && !_filteredLandmarks.any((l) => l.id == _selectedLandmark?.id)) { _selectedLandmark = _filteredLandmarks[0]; } }); } void _onRegionSelected(String region) { setState(() { _selectedRegion = region; _filteredLandmarks = LandmarkDatabase.search( _searchController.text, region: region, ); if (_filteredLandmarks.isNotEmpty && !_filteredLandmarks.any((l) => l.id == _selectedLandmark?.id)) { _selectedLandmark = _filteredLandmarks[0]; } }); } void _lockCurrentObservation() { if (_selectedLandmark == null) return; final trueAzimuth = ResectionCalculator.getTrueAzimuth(_headingDeg); final obs = ResectionObservation( landmark: _selectedLandmark!, observedAzimuthDeg: _headingDeg, trueAzimuthDeg: trueAzimuth, ); setState(() => _isLockedAnim = true); Future.delayed(const Duration(milliseconds: 300), () { if (mounted) setState(() => _isLockedAnim = false); }); widget.onObservationAdded(obs); final remaining = _filteredLandmarks .where((l) => l.id != _selectedLandmark!.id && !widget.observations.any((o) => o.landmark.id == l.id)) .toList(); if (remaining.isNotEmpty) { setState(() => _selectedLandmark = remaining[0]); } } @override Widget build(BuildContext context) { final trueAzimuth = ResectionCalculator.getTrueAzimuth(_headingDeg); final obsCount = widget.observations.length; return Scaffold( backgroundColor: Colors.black, body: Stack( children: [ // ── 1. Camera Feed / Viewfinder Background ───────────────── if (_isCameraReady && _cameraController != null) SizedBox.expand( child: FittedBox( fit: BoxFit.cover, child: SizedBox( width: _cameraController!.value.previewSize?.height ?? 1, height: _cameraController!.value.previewSize?.width ?? 1, child: CameraPreview(_cameraController!), ), ), ) else Container( decoration: const BoxDecoration( gradient: RadialGradient( center: Alignment.center, radius: 1.2, colors: [Color(0xFF0F172A), Color(0xFF020617)], ), ), child: Center( child: Column( mainAxisSize: MainAxisSize.min, children: [ Container( padding: const EdgeInsets.all(16), decoration: BoxDecoration( color: const Color(0x330071E3), borderRadius: BorderRadius.circular(50), border: Border.all(color: const Color(0x660071E3)), ), child: const Icon(Icons.camera_alt, size: 36, color: Color(0xFF38BDF8)), ), const SizedBox(height: 12), const Text( 'منظار الاستطلاع والتسديد البصري (HUD)', style: TextStyle(color: Colors.white, fontSize: 13, fontWeight: FontWeight.bold), ), const SizedBox(height: 4), const Text( 'وجه الكاميرا أو حرك مؤشر السمت للتسديد على المعلم', style: TextStyle(color: Color(0xFF94A3B8), fontSize: 11), ), ], ), ), ), // ── 2. Tactical Military Crosshair & Reticle ──────────────── TacticalCrosshair( headingDeg: _headingDeg, trueAzimuthDeg: trueAzimuth, angleUnit: widget.angleUnit, isAimLocked: _isLockedAnim, targetName: _selectedLandmark?.name, ), // ── 3. Top Header: Step Flow + Close Button ───────────────── Positioned( top: 48, left: 16, right: 16, child: Column( children: [ Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ // Step Flow Badge Container( padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 6), decoration: BoxDecoration( color: const Color(0xEB0F172A), borderRadius: BorderRadius.circular(20), border: Border.all( color: obsCount >= 2 ? const Color(0xFF22C55E) : const Color(0xFF0071E3), ), ), child: Row( children: [ Icon( obsCount >= 2 ? Icons.check_circle : Icons.gps_fixed, size: 14, color: obsCount >= 2 ? const Color(0xFF4ADE80) : const Color(0xFF38BDF8), ), const SizedBox(width: 6), Text( obsCount == 0 ? 'الخطوة 1: تسديد المعلم الأول' : (obsCount == 1 ? 'الخطوة 2: تسديد المعلم الثاني' : 'تم رصد $obsCount معالم (جاهز للحساب)'), style: TextStyle( color: obsCount >= 2 ? const Color(0xFF4ADE80) : Colors.white, fontSize: 11, fontWeight: FontWeight.bold, ), ), ], ), ), // Close / Return to Map Button IconButton.filled( onPressed: widget.onClose, icon: const Icon(Icons.close, size: 16), style: IconButton.styleFrom( backgroundColor: const Color(0xCC0F172A), foregroundColor: Colors.white, ), ), ], ), const SizedBox(height: 10), // Search Bar for Landmarks Container( decoration: BoxDecoration( color: const Color(0xEB0F172A), borderRadius: BorderRadius.circular(12), border: Border.all(color: Colors.white12), ), child: TextField( controller: _searchController, onChanged: _onSearchChanged, style: const TextStyle(color: Colors.white, fontSize: 12), decoration: InputDecoration( hintText: 'ابحث عن معلم بالاسم أو المحافظة أو النوع...', hintStyle: const TextStyle(color: Color(0xFF64748B), fontSize: 11), prefixIcon: const Icon(Icons.search, color: Color(0xFF38BDF8), size: 18), suffixIcon: _searchController.text.isNotEmpty ? IconButton( icon: const Icon(Icons.clear, size: 14, color: Colors.white60), onPressed: () { _searchController.clear(); _onSearchChanged(''); }, ) : null, contentPadding: const EdgeInsets.symmetric(vertical: 10), border: InputBorder.none, ), ), ), const SizedBox(height: 6), // Region filter horizontal pills SizedBox( height: 28, child: ListView.builder( scrollDirection: Axis.horizontal, itemCount: _regions.length, itemBuilder: (context, i) { final r = _regions[i]; final isSelected = r == _selectedRegion; return Padding( padding: const EdgeInsets.only(left: 6), child: ChoiceChip( label: Text(r, style: TextStyle(fontSize: 10, color: isSelected ? Colors.white : const Color(0xFF94A3B8))), selected: isSelected, onSelected: (_) => _onRegionSelected(r), backgroundColor: const Color(0x990F172A), selectedColor: const Color(0xFF0071E3), padding: const EdgeInsets.symmetric(horizontal: 4), materialTapTargetSize: MaterialTapTargetSize.shrinkWrap, shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(14)), ), ); }, ), ), ], ), ), // ── 4. Bottom Control Panel ──────────────────────────────── Positioned( bottom: 20, left: 16, right: 16, child: Container( padding: const EdgeInsets.all(14), decoration: BoxDecoration( color: const Color(0xF50F172A), borderRadius: BorderRadius.circular(18), border: Border.all(color: const Color(0x800071E3)), boxShadow: const [BoxShadow(color: Colors.black87, blurRadius: 25)], ), child: Column( mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.start, children: [ // Heading Slider fine-tune (for desktop/field override) Row( children: [ const Text('ضبط السمت:', style: TextStyle(fontSize: 10, color: Color(0xFF94A3B8))), Expanded( child: Slider( value: _headingDeg, min: 0, max: 359, activeColor: const Color(0xFF0071E3), onChanged: (v) => setState(() => _headingDeg = v), ), ), Text( AngleFormatter.format(_headingDeg, widget.angleUnit, includeLabel: false), style: const TextStyle(fontSize: 11, fontWeight: FontWeight.bold, color: Color(0xFF38BDF8)), ), ], ), // Landmark Selector Cards (Horizontal Carousel) if (_filteredLandmarks.isNotEmpty) SizedBox( height: 52, child: ListView.builder( scrollDirection: Axis.horizontal, itemCount: _filteredLandmarks.length, itemBuilder: (context, idx) { final lm = _filteredLandmarks[idx]; final isSelected = lm.id == _selectedLandmark?.id; final isObserved = widget.observations.any((o) => o.landmark.id == lm.id); return GestureDetector( onTap: () => setState(() => _selectedLandmark = lm), child: Container( margin: const EdgeInsets.only(left: 8), padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 6), decoration: BoxDecoration( color: isSelected ? const Color(0xFF0071E3) : (isObserved ? const Color(0x3322C55E) : const Color(0xFF1E293B)), borderRadius: BorderRadius.circular(10), border: Border.all( color: isSelected ? Colors.white : (isObserved ? const Color(0xFF22C55E) : Colors.white12), ), ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, mainAxisAlignment: MainAxisAlignment.center, children: [ Row( children: [ if (isObserved) const Icon(Icons.check_circle, size: 12, color: Color(0xFF4ADE80)), if (isObserved) const SizedBox(width: 4), Text( lm.name, style: TextStyle( fontSize: 11, fontWeight: FontWeight.bold, color: isSelected ? Colors.white : const Color(0xFFE2E8F0), ), ), ], ), Text( '${lm.region} • ${lm.elevationM}م', style: TextStyle( fontSize: 9.5, color: isSelected ? Colors.white70 : const Color(0xFF94A3B8), ), ), ], ), ), ); }, ), ), const SizedBox(height: 12), // Action Buttons: Lock Landmark + Calculate Button Row( children: [ // Lock Target Button Expanded( flex: 3, child: ElevatedButton.icon( onPressed: _lockCurrentObservation, icon: const Icon(Icons.lock_outline, size: 16), label: Text( obsCount == 0 ? 'تثبيت رصد المعلم الأول' : (obsCount == 1 ? 'تثبيت رصد المعلم الثاني' : 'تثبيت معلم إضافي (+1)'), style: const TextStyle(fontSize: 12, fontWeight: FontWeight.bold), ), style: ElevatedButton.styleFrom( backgroundColor: const Color(0xFF0071E3), foregroundColor: Colors.white, padding: const EdgeInsets.symmetric(vertical: 12), shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(10)), ), ), ), // Calculate & Show on Map (Available when >= 2) if (obsCount >= 2) ...[ const SizedBox(width: 8), Expanded( flex: 2, child: ElevatedButton.icon( onPressed: widget.onCalculatePressed, icon: const Icon(Icons.map, size: 16), label: const Text( 'استخراج الموقع', style: TextStyle(fontSize: 11.5, fontWeight: FontWeight.w900), ), style: ElevatedButton.styleFrom( backgroundColor: const Color(0xFF22C55E), foregroundColor: Colors.white, padding: const EdgeInsets.symmetric(vertical: 12), shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(10)), ), ), ), ], // Reset Button if (obsCount > 0) ...[ const SizedBox(width: 8), IconButton.filledTonal( onPressed: widget.onResetPressed, icon: const Icon(Icons.refresh, size: 16), style: IconButton.styleFrom( backgroundColor: const Color(0x33EF4444), foregroundColor: const Color(0xFFF87171), ), ), ], ], ), ], ), ), ), ], ), ); } }