// lib/screens/search_screen.dart import 'dart:ui'; import 'package:flutter/material.dart'; // Location packages import 'package:geolocator/geolocator.dart'; import 'package:geocoding/geocoding.dart'; /// Data model for a location suggestion (city + optional pincode). class _LocationItem { final String city; final String? district; final String? pincode; const _LocationItem({required this.city, this.district, this.pincode}); String get displayTitle => district != null && district!.isNotEmpty ? '$city, $district' : city; String get displaySubtitle => pincode ?? ''; /// What gets returned to the caller (city name + optional district for display in pill). String get returnValue => displayTitle; } class SearchScreen extends StatefulWidget { const SearchScreen({Key? key}) : super(key: key); @override State createState() => _SearchScreenState(); } class _SearchScreenState extends State { final TextEditingController _ctrl = TextEditingController(); /// Popular Kerala cities shown as chips. static const List _popularCities = [ 'Thiruvananthapuram', 'Kochi', 'Kozhikode', 'Kollam', 'Thrissur', 'Kannur', 'Alappuzha', 'Palakkad', 'Malappuram', 'Kottayam', ]; /// Searchable location database – Kerala towns/cities with pincodes. static const List<_LocationItem> _locationDb = [ _LocationItem(city: 'Thiruvananthapuram', district: 'Thiruvananthapuram', pincode: '695001'), _LocationItem(city: 'Kazhakoottam', district: 'Thiruvananthapuram', pincode: '695582'), _LocationItem(city: 'Neyyattinkara', district: 'Thiruvananthapuram', pincode: '695121'), _LocationItem(city: 'Attingal', district: 'Thiruvananthapuram', pincode: '695101'), _LocationItem(city: 'Kochi', district: 'Ernakulam', pincode: '682001'), _LocationItem(city: 'Ernakulam', district: 'Ernakulam', pincode: '682011'), _LocationItem(city: 'Aluva', district: 'Ernakulam', pincode: '683101'), _LocationItem(city: 'Kakkanad', district: 'Ernakulam', pincode: '682030'), _LocationItem(city: 'Fort Kochi', district: 'Ernakulam', pincode: '682001'), _LocationItem(city: 'Kozhikode', district: 'Kozhikode', pincode: '673001'), _LocationItem(city: 'Feroke', district: 'Kozhikode', pincode: '673631'), _LocationItem(city: 'Kollam', district: 'Kollam', pincode: '691001'), _LocationItem(city: 'Karunagappally', district: 'Kollam', pincode: '690518'), _LocationItem(city: 'Thrissur', district: 'Thrissur', pincode: '680001'), _LocationItem(city: 'Chavakkad', district: 'Thrissur', pincode: '680506'), _LocationItem(city: 'Guruvayoor', district: 'Thrissur', pincode: '680101'), _LocationItem(city: 'Irinjalakuda', district: 'Thrissur', pincode: '680121'), _LocationItem(city: 'Kannur', district: 'Kannur', pincode: '670001'), _LocationItem(city: 'Thalassery', district: 'Kannur', pincode: '670101'), _LocationItem(city: 'Alappuzha', district: 'Alappuzha', pincode: '688001'), _LocationItem(city: 'Cherthala', district: 'Alappuzha', pincode: '688524'), _LocationItem(city: 'Palakkad', district: 'Palakkad', pincode: '678001'), _LocationItem(city: 'Ottapalam', district: 'Palakkad', pincode: '679101'), _LocationItem(city: 'Malappuram', district: 'Malappuram', pincode: '676505'), _LocationItem(city: 'Manjeri', district: 'Malappuram', pincode: '676121'), _LocationItem(city: 'Tirur', district: 'Malappuram', pincode: '676101'), _LocationItem(city: 'Kottayam', district: 'Kottayam', pincode: '686001'), _LocationItem(city: 'Pala', district: 'Kottayam', pincode: '686575'), _LocationItem(city: 'Pathanamthitta', district: 'Pathanamthitta', pincode: '689645'), _LocationItem(city: 'Idukki', district: 'Idukki', pincode: '685602'), _LocationItem(city: 'Munnar', district: 'Idukki', pincode: '685612'), _LocationItem(city: 'Wayanad', district: 'Wayanad', pincode: '673121'), _LocationItem(city: 'Kalpetta', district: 'Wayanad', pincode: '673121'), _LocationItem(city: 'Kasaragod', district: 'Kasaragod', pincode: '671121'), _LocationItem(city: 'Whitefield', district: 'Bengaluru', pincode: '560066'), _LocationItem(city: 'Bengaluru', district: 'Karnataka', pincode: '560001'), ]; List<_LocationItem> _searchResults = []; bool _showSearchResults = false; bool _loadingLocation = false; @override void dispose() { _ctrl.dispose(); super.dispose(); } void _onQueryChanged(String q) { final ql = q.trim().toLowerCase(); setState(() { if (ql.isEmpty) { _showSearchResults = false; _searchResults = []; } else { _showSearchResults = true; _searchResults = _locationDb.where((loc) { return loc.city.toLowerCase().contains(ql) || (loc.district?.toLowerCase().contains(ql) ?? false) || (loc.pincode?.contains(ql) ?? false); }).toList(); } }); } void _selectAndClose(String location) { Navigator.of(context).pop(location); } Future _useCurrentLocation() async { setState(() => _loadingLocation = true); try { LocationPermission permission = await Geolocator.checkPermission(); if (permission == LocationPermission.denied) { permission = await Geolocator.requestPermission(); } if (permission == LocationPermission.deniedForever || permission == LocationPermission.denied) { if (mounted) { ScaffoldMessenger.of(context).showSnackBar(const SnackBar(content: Text('Location permission denied'))); Navigator.of(context).pop('Current Location'); } return; } final pos = await Geolocator.getCurrentPosition(desiredAccuracy: LocationAccuracy.best); try { final placemarks = await placemarkFromCoordinates(pos.latitude, pos.longitude); if (placemarks.isNotEmpty) { final p = placemarks.first; final parts = []; if ((p.subLocality ?? '').isNotEmpty) parts.add(p.subLocality!); if ((p.locality ?? '').isNotEmpty) parts.add(p.locality!); if ((p.subAdministrativeArea ?? '').isNotEmpty) parts.add(p.subAdministrativeArea!); final label = parts.isNotEmpty ? parts.join(', ') : 'Current Location'; if (mounted) Navigator.of(context).pop(label); return; } } catch (_) {} if (mounted) Navigator.of(context).pop('Current Location'); } catch (e) { if (mounted) { ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text('Could not determine location: $e'))); Navigator.of(context).pop('Current Location'); } } finally { if (mounted) setState(() => _loadingLocation = false); } } @override Widget build(BuildContext context) { return Scaffold( backgroundColor: Colors.transparent, body: GestureDetector( onTap: () => Navigator.of(context).pop(), behavior: HitTestBehavior.opaque, child: Stack( children: [ RepaintBoundary( child: BackdropFilter( filter: ImageFilter.blur(sigmaX: 8.0, sigmaY: 8.0), child: Container(color: Colors.black.withOpacity(0.16)), ), ), Align( alignment: Alignment.bottomCenter, child: GestureDetector( onTap: () {}, // prevent taps on sheet from closing child: _buildSheet(context), ), ), ], ), ), ); } Widget _buildSheet(BuildContext context) { return Container( constraints: BoxConstraints(maxHeight: MediaQuery.of(context).size.height * 0.78, minHeight: 240), decoration: const BoxDecoration( color: Colors.white, borderRadius: BorderRadius.vertical(top: Radius.circular(24)), boxShadow: [BoxShadow(color: Colors.black26, blurRadius: 12)], ), child: SafeArea( top: false, child: SingleChildScrollView( padding: const EdgeInsets.fromLTRB(20, 18, 20, 28), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ // Header row Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ const Text('Set Your Location', style: TextStyle(fontSize: 22, fontWeight: FontWeight.bold, color: Color(0xFF1A1A2E))), InkWell( onTap: () => Navigator.of(context).pop(), borderRadius: BorderRadius.circular(12), child: Container( width: 40, height: 40, decoration: BoxDecoration(color: const Color(0xFF1A1A2E), borderRadius: BorderRadius.circular(10)), child: const Icon(Icons.close, color: Colors.white, size: 20), ), ), ], ), const SizedBox(height: 16), // Search field Container( padding: const EdgeInsets.symmetric(horizontal: 14), decoration: BoxDecoration( color: Colors.grey[100], borderRadius: BorderRadius.circular(14), border: _ctrl.text.isNotEmpty ? Border.all(color: const Color(0xFF2563EB).withOpacity(0.5), width: 1.5) : null, ), child: Row( children: [ Icon(Icons.search, color: Colors.grey[500]), const SizedBox(width: 10), Expanded( child: TextField( controller: _ctrl, decoration: const InputDecoration( hintText: 'Search city, area or locality', hintStyle: TextStyle(color: Color(0xFF9CA3AF)), border: InputBorder.none, contentPadding: EdgeInsets.symmetric(vertical: 14), ), textInputAction: TextInputAction.search, onChanged: _onQueryChanged, onSubmitted: (v) { final q = v.trim(); if (q.isEmpty) return; if (_searchResults.isNotEmpty) { _selectAndClose(_searchResults.first.returnValue); } else { _selectAndClose(q); } }, ), ), if (_ctrl.text.isNotEmpty) IconButton( icon: const Icon(Icons.clear, size: 20), onPressed: () { _ctrl.clear(); _onQueryChanged(''); }, ), ], ), ), const SizedBox(height: 16), // Use current location Material( color: const Color(0xFF2563EB), borderRadius: BorderRadius.circular(14), child: InkWell( onTap: _loadingLocation ? null : () => _useCurrentLocation(), borderRadius: BorderRadius.circular(14), child: Padding( padding: const EdgeInsets.symmetric(vertical: 16, horizontal: 16), child: Row( children: [ const Icon(Icons.my_location, color: Colors.white, size: 22), const SizedBox(width: 12), Expanded( child: Text( _loadingLocation ? 'Detecting location...' : 'Use Current Location', style: const TextStyle(color: Colors.white, fontWeight: FontWeight.w600, fontSize: 15), ), ), if (_loadingLocation) const SizedBox(width: 20, height: 20, child: CircularProgressIndicator(color: Colors.white, strokeWidth: 2)) else const Icon(Icons.chevron_right, color: Colors.white), ], ), ), ), ), const SizedBox(height: 20), // Search results or Popular Cities if (_showSearchResults) ...[ if (_searchResults.isEmpty) Padding( padding: const EdgeInsets.symmetric(vertical: 24), child: Center(child: Text('No results found', style: TextStyle(color: Colors.grey[500]))), ) else ConstrainedBox( constraints: const BoxConstraints(maxHeight: 320), child: ListView.separated( shrinkWrap: false, physics: const ClampingScrollPhysics(), itemCount: _searchResults.length, separatorBuilder: (_, __) => Divider(color: Colors.grey[200], height: 1), itemBuilder: (ctx, idx) { final loc = _searchResults[idx]; return ListTile( contentPadding: const EdgeInsets.symmetric(horizontal: 4, vertical: 4), leading: Icon(Icons.location_on_outlined, color: Colors.grey[400], size: 24), title: Text( loc.displayTitle, style: const TextStyle(fontWeight: FontWeight.w600, fontSize: 15, color: Color(0xFF1A1A2E)), ), subtitle: loc.pincode != null && loc.pincode!.isNotEmpty ? Text(loc.pincode!, style: TextStyle(color: Colors.grey[500], fontSize: 13)) : null, onTap: () => _selectAndClose(loc.returnValue), ); }, ), ), ] else ...[ const Text('Popular Cities', style: TextStyle(fontWeight: FontWeight.bold, fontSize: 16, color: Color(0xFF1A1A2E))), const SizedBox(height: 12), Wrap( spacing: 10, runSpacing: 10, children: [ for (final city in _popularCities) InkWell( onTap: () => _selectAndClose(city), borderRadius: BorderRadius.circular(12), child: Container( padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12), decoration: BoxDecoration(color: const Color(0xFFF3F4F6), borderRadius: BorderRadius.circular(12)), child: Text( city.length > 16 ? '${city.substring(0, 14)}...' : city, style: const TextStyle(color: Color(0xFF374151), fontWeight: FontWeight.w500, fontSize: 14), ), ), ), ], ), ], const SizedBox(height: 8), ], ), ), ), ); } }