import 'package:flutter/material.dart'; import '../models/landmark.dart'; import '../services/landmark_database.dart'; import '../services/local_sqlite_db.dart'; import '../services/tactical_api_service.dart'; class PlaceSearchSheet extends StatefulWidget { final Function(TacticalLandmark, bool startNavigation) onDestinationSelected; const PlaceSearchSheet({super.key, required this.onDestinationSelected}); @override State createState() => _PlaceSearchSheetState(); } class _PlaceSearchSheetState extends State { final TextEditingController _controller = TextEditingController(); List _results = []; bool _isSearching = false; @override void initState() { super.initState(); _results = LandmarkDatabase.allLandmarks.take(15).toList(); } void _onSearch(String query) async { final cleanQuery = query.trim(); if (cleanQuery.isEmpty) { setState(() => _results = LandmarkDatabase.allLandmarks.take(15).toList()); return; } setState(() => _isSearching = true); // 1. Search SQLite Database (Instant offline search) List dbResults = []; try { dbResults = await LocalSqliteDb.searchPlaces(cleanQuery, limit: 30); } catch (_) {} // 2. Search Static Landmark database final staticResults = LandmarkDatabase.search(cleanQuery); // 3. Search Online Geocoding API if offline results are few List onlineResults = []; if (dbResults.length < 5) { try { onlineResults = await TacticalApiService.searchOnlinePlaces(cleanQuery); } catch (_) {} } final combined = [...dbResults, ...onlineResults, ...staticResults]; final seen = {}; final unique = []; for (final r in combined) { if (seen.add('${r.name}_${r.lat}_${r.lng}')) { unique.add(r); } } if (mounted) { setState(() { _results = unique; _isSearching = false; }); } } @override Widget build(BuildContext context) { return Container( padding: const EdgeInsets.only(top: 16, left: 16, right: 16, bottom: 24), decoration: const BoxDecoration( color: Color(0xFF0F172A), borderRadius: BorderRadius.vertical(top: Radius.circular(24)), boxShadow: [BoxShadow(color: Colors.black87, blurRadius: 25)], ), child: Column( mainAxisSize: MainAxisSize.min, children: [ // Drag Handle Container( width: 40, height: 4, decoration: BoxDecoration( color: Colors.white24, borderRadius: BorderRadius.circular(2), ), ), const SizedBox(height: 14), // Search Field TextField( controller: _controller, onChanged: _onSearch, style: const TextStyle(color: Colors.white, fontSize: 13), decoration: InputDecoration( hintText: 'ابحث عن أي وجهة، شارع، معلم، أو منشأة في الأردن...', hintStyle: const TextStyle(color: Color(0xFF64748B), fontSize: 11.5), prefixIcon: const Icon(Icons.search, color: Color(0xFF0071E3), size: 20), suffixIcon: _controller.text.isNotEmpty ? IconButton( icon: const Icon(Icons.clear, size: 16, color: Colors.white54), onPressed: () { _controller.clear(); _onSearch(''); }, ) : null, filled: true, fillColor: const Color(0xFF020617), contentPadding: const EdgeInsets.symmetric(vertical: 12), border: OutlineInputBorder( borderRadius: BorderRadius.circular(14), borderSide: const BorderSide(color: Colors.white12), ), focusedBorder: OutlineInputBorder( borderRadius: BorderRadius.circular(14), borderSide: const BorderSide(color: Color(0xFF0071E3), width: 1.5), ), ), ), if (_isSearching) ...[ const SizedBox(height: 4), const LinearProgressIndicator(minHeight: 2, color: Color(0xFF0071E3), backgroundColor: Colors.transparent), ], const SizedBox(height: 12), // Results List ConstrainedBox( constraints: const BoxConstraints(maxHeight: 280), child: _results.isEmpty ? const Padding( padding: EdgeInsets.all(24), child: Text( 'لم يتم العثور على نتائج مطابقة', style: TextStyle(color: Color(0xFF94A3B8), fontSize: 12), ), ) : ListView.separated( shrinkWrap: true, itemCount: _results.length, separatorBuilder: (_, __) => const Divider(color: Colors.white10, height: 1), itemBuilder: (context, index) { final item = _results[index]; return ListTile( contentPadding: const EdgeInsets.symmetric(horizontal: 4, vertical: 2), leading: Container( padding: const EdgeInsets.all(8), decoration: BoxDecoration( color: const Color(0xFF1E293B), borderRadius: BorderRadius.circular(8), ), child: Icon(item.typeIcon, color: const Color(0xFF38BDF8), size: 18), ), title: Text( item.name, style: const TextStyle(color: Colors.white, fontSize: 12.5, fontWeight: FontWeight.bold), ), subtitle: Text( '${item.region} • ${item.typeLabelArabic}', style: const TextStyle(color: Color(0xFF94A3B8), fontSize: 10.5), ), onTap: () { Navigator.pop(context); widget.onDestinationSelected(item, false); // Preview route with fit bounds }, trailing: ElevatedButton.icon( onPressed: () { Navigator.pop(context); widget.onDestinationSelected(item, true); // Direct start navigation }, icon: const Icon(Icons.navigation, size: 13), label: const Text('انطلق', style: TextStyle(fontSize: 11)), style: ElevatedButton.styleFrom( backgroundColor: const Color(0xFF0071E3), foregroundColor: Colors.white, padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 6), minimumSize: Size.zero, tapTargetSize: MaterialTapTargetSize.shrinkWrap, shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)), ), ), ); }, ), ), ], ), ); } }