// lib/screens/search_screen.dart import 'dart:ui'; import 'package:flutter/material.dart'; // Location packages (add to pubspec.yaml) // geolocator -> for permission & coordinates // geocoding -> for reverse geocoding coordinates to a placemark import 'package:geolocator/geolocator.dart'; import 'package:geocoding/geocoding.dart'; class SearchScreen extends StatefulWidget { const SearchScreen({Key? key}) : super(key: key); /// Returns a String to the caller via Navigator.pop(string). /// Could be: /// - a city name (e.g. "Bengaluru") /// - 'Current Location' or a resolved locality like "Whitefield, Bengaluru" @override State createState() => _SearchScreenState(); } class _SearchScreenState extends State { final TextEditingController _ctrl = TextEditingController(); final List _popularCities = const [ 'Delhi NCR', 'Mumbai', 'Kolkata', 'Bengaluru', 'Hyderabad', 'Chandigarh', 'Pune', 'Chennai', 'Ahmedabad', 'Jaipur', ]; List _filtered = []; bool _loadingLocation = false; @override void initState() { super.initState(); _filtered = List.from(_popularCities); } @override void dispose() { _ctrl.dispose(); super.dispose(); } void _onQueryChanged(String q) { final ql = q.trim().toLowerCase(); setState(() { if (ql.isEmpty) { _filtered = List.from(_popularCities); } else { _filtered = _popularCities.where((c) => c.toLowerCase().contains(ql)).toList(); } }); } void _selectAndClose(String city) { Navigator.of(context).pop(city); } Future _useCurrentLocation() async { setState(() => _loadingLocation = true); try { // Check / request permission LocationPermission permission = await Geolocator.checkPermission(); if (permission == LocationPermission.denied) { permission = await Geolocator.requestPermission(); } if (permission == LocationPermission.deniedForever || permission == LocationPermission.denied) { // Can't get permission — inform user and return a fallback label ScaffoldMessenger.of(context).showSnackBar(const SnackBar(content: Text('Location permission denied'))); Navigator.of(context).pop('Current Location'); return; } // Get current position final pos = await Geolocator.getCurrentPosition(desiredAccuracy: LocationAccuracy.best); // Try reverse geocoding to get a readable place name 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!); if ((p.administrativeArea ?? '').isNotEmpty) parts.add(p.administrativeArea!); final label = parts.isNotEmpty ? parts.join(', ') : 'Current Location'; Navigator.of(context).pop(label); return; } } catch (_) { // ignore reverse geocode failures and fallback to coordinates or simple label } // fallback: return lat,lng string or simple label Navigator.of(context).pop('${pos.latitude.toStringAsFixed(5)},${pos.longitude.toStringAsFixed(5)}'); } catch (e) { // If any error, fallback to simple label 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) { // Full-screen transparent Scaffold so the BackdropFilter can blur underlying UI. return Scaffold( backgroundColor: Colors.transparent, body: GestureDetector( // Tap outside sheet to dismiss onTap: () => Navigator.of(context).pop(), behavior: HitTestBehavior.opaque, child: Stack( children: [ // BackdropFilter + dim overlay BackdropFilter( filter: ImageFilter.blur(sigmaX: 8.0, sigmaY: 8.0), child: Container(color: Colors.black.withOpacity(0.16)), ), // Align bottom: the sheet content Align( alignment: Alignment.bottomCenter, child: _SearchBottomSheet( controller: _ctrl, filteredCities: _filtered, onCityTap: (city) => _selectAndClose(city), onQueryChanged: _onQueryChanged, onUseCurrentLocation: _useCurrentLocation, loadingLocation: _loadingLocation, ), ), ], ), ), ); } } class _SearchBottomSheet extends StatelessWidget { final TextEditingController controller; final List filteredCities; final void Function(String) onCityTap; final void Function(String) onQueryChanged; final Future Function() onUseCurrentLocation; final bool loadingLocation; const _SearchBottomSheet({ Key? key, required this.controller, required this.filteredCities, required this.onCityTap, required this.onQueryChanged, required this.onUseCurrentLocation, required this.loadingLocation, }) : super(key: key); Widget _cityChip(String name, BuildContext context, void Function() onTap) { return InkWell( onTap: onTap, borderRadius: BorderRadius.circular(12), child: Container( padding: const EdgeInsets.symmetric(horizontal: 14, vertical: 12), decoration: BoxDecoration(color: Colors.grey[100], borderRadius: BorderRadius.circular(12)), child: Text(name, style: const TextStyle(color: Colors.black87)), ), ); } @override Widget build(BuildContext context) { // The bottom sheet container return Padding( padding: const EdgeInsets.all(0), child: Container( // limit height so it looks like a sheet 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: [ // center drag handle Center( child: Container(width: 48, height: 6, decoration: BoxDecoration(color: Colors.grey[300], borderRadius: BorderRadius.circular(6))), ), const SizedBox(height: 12), // Header Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ const Text('Set Your Location', style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold)), // Close button (inside sheet) InkWell( onTap: () => Navigator.of(context).pop(), borderRadius: BorderRadius.circular(12), child: Container(width: 40, height: 40, decoration: BoxDecoration(color: Colors.grey[100], borderRadius: BorderRadius.circular(10)), child: const Icon(Icons.close, color: Colors.black54)), ), ], ), const SizedBox(height: 14), // Search field (now functional) Container( padding: const EdgeInsets.symmetric(horizontal: 12), decoration: BoxDecoration(color: Colors.grey[100], borderRadius: BorderRadius.circular(12)), child: Row( children: [ const Icon(Icons.search, color: Colors.black38), const SizedBox(width: 10), Expanded( child: TextField( controller: controller, decoration: const InputDecoration(hintText: 'Search city, area or locality', border: InputBorder.none), textInputAction: TextInputAction.search, onChanged: onQueryChanged, onSubmitted: (v) { final q = v.trim(); if (q.isEmpty) return; // If there's an exact/first match in filteredCities, pick it; otherwise pass the raw query. final match = filteredCities.isNotEmpty ? filteredCities.first : null; Navigator.of(context).pop(match ?? q); }, ), ), if (controller.text.isNotEmpty) IconButton( icon: const Icon(Icons.clear), onPressed: () { controller.clear(); onQueryChanged(''); }, ), ], ), ), const SizedBox(height: 14), // Use current location button ElevatedButton( onPressed: loadingLocation ? null : () => onUseCurrentLocation(), style: ElevatedButton.styleFrom( padding: const EdgeInsets.symmetric(vertical: 16, horizontal: 12), backgroundColor: const Color(0xFF0B63D6), shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)), ), child: Row( children: [ const Icon(Icons.my_location, color: Colors.white), const SizedBox(width: 12), Expanded(child: Text(loadingLocation ? 'Detecting location...' : 'Use Current Location', style: const TextStyle(color: Colors.white))), if (loadingLocation) const SizedBox(width: 18, height: 18, child: CircularProgressIndicator(color: Colors.white, strokeWidth: 2)) else const Icon(Icons.chevron_right, color: Colors.white), ], ), ), const SizedBox(height: 18), // Popular cities const Text('Popular Cities', style: TextStyle(fontWeight: FontWeight.bold)), const SizedBox(height: 12), Wrap( spacing: 12, runSpacing: 12, children: [ for (final city in filteredCities.take(8)) _cityChip(city, context, () => onCityTap(city)), // if filteredCities is empty show empty state if (filteredCities.isEmpty) Padding( padding: const EdgeInsets.symmetric(vertical: 12), child: Text('No suggestions', style: TextStyle(color: Colors.grey[600])), ) ], ), const SizedBox(height: 8), ], ), ), ), ), ); } }