Aidra Driver 1.3.5+68
Aidra Driver - Your path to green energy
Loading...
Searching...
No Matches
location_search_bar.dart
Go to the documentation of this file.
1import 'package:easy_localization/easy_localization.dart';
2import 'package:flutter/material.dart';
3import 'package:flutter_screenutil/flutter_screenutil.dart';
4import 'package:fluentui_system_icons/fluentui_system_icons.dart';
5import 'package:google_maps_flutter/google_maps_flutter.dart';
6import 'package:geocoding/geocoding.dart';
7import 'dart:async';
8
9import '../../../../../../core/ui/theme/color_palette.dart';
10import '../models/search_result.dart';
11
12class LocationSearchBar extends StatefulWidget {
13 final TextEditingController controller;
14 final Function(LatLng) onLocationSelected;
15 final bool isVisible;
16 final VoidCallback? onClose;
17
19 super.key,
20 required this.controller,
21 required this.onLocationSelected,
22 this.isVisible = true,
23 this.onClose,
24 });
25
26 @override
27 State<LocationSearchBar> createState() => _LocationSearchBarState();
28}
29
30class _LocationSearchBarState extends State<LocationSearchBar> {
31 bool _isSearching = false;
32 List<SearchResult> _searchResults = [];
33 final FocusNode _focusNode = FocusNode();
35
36 bool _isSearchError = false;
37
39 // Cancel previous timer
40 _debounceTimer?.cancel();
41
42 if (query.isEmpty) {
43 setState(() {
44 _isSearching = false;
45 _searchResults = [];
46 _isSearchError = false;
47 });
48 return;
49 }
50
51 // Set new timer to debounce the search
52 _debounceTimer = Timer(const Duration(milliseconds: 500), () {
54 });
55 }
56
57 Future<void> _performGeocodingSearch(String query) async {
58 setState(() {
59 _isSearching = true;
60 _isSearchError = false;
61 });
62
63 try {
64 // Use geocoding package to search for locations
65 List<Location> locations = await locationFromAddress(query);
66
67 List<SearchResult> results = [];
68
69 // Convert Location objects to SearchResult objects
70 for (int i = 0; i < locations.length && i < 5; i++) {
71 final location = locations[i];
72
73 try {
74 // Get detailed address information for each location
75 List<Placemark> placemarks = await placemarkFromCoordinates(
76 location.latitude,
77 location.longitude,
78 );
79
80 if (placemarks.isNotEmpty) {
81 final placemark = placemarks.first;
82
83 String name = query;
85
86 // If we have a more specific name from the placemark, use it
87 if (placemark.name != null && placemark.name!.isNotEmpty) {
88 name = placemark.name!;
89 } else if (placemark.thoroughfare != null && placemark.thoroughfare!.isNotEmpty) {
90 name = placemark.thoroughfare!;
91 }
92
93 results.add(SearchResult(
94 name: name,
96 latLng: LatLng(location.latitude, location.longitude),
97 ));
98 }
99 } catch (e) {
100 // If reverse geocoding fails for this location, add it with basic info
101 results.add(SearchResult(
102 name: query,
103 address: '${location.latitude.toStringAsFixed(4)}, ${location.longitude.toStringAsFixed(4)}',
104 latLng: LatLng(location.latitude, location.longitude),
105 ));
106 }
107 }
108
109 if (mounted) {
110 setState(() {
111 _searchResults = results;
112 _isSearching = false;
113 _isSearchError = results.isEmpty;
114 });
115 }
116 } catch (e) {
117 if (mounted) {
118 setState(() {
119 _searchResults = [];
120 _isSearching = false;
121 _isSearchError = true;
122 });
123 }
124 }
125 }
126
127 String _buildAddressString(Placemark placemark) {
128 List<String> addressParts = [];
129
130 if (placemark.thoroughfare != null && placemark.thoroughfare!.isNotEmpty) {
131 addressParts.add(placemark.thoroughfare!);
132 }
133 if (placemark.locality != null && placemark.locality!.isNotEmpty) {
134 addressParts.add(placemark.locality!);
135 }
136 if (placemark.administrativeArea != null && placemark.administrativeArea!.isNotEmpty) {
137 addressParts.add(placemark.administrativeArea!);
138 }
139 if (placemark.country != null && placemark.country!.isNotEmpty) {
140 addressParts.add(placemark.country!);
141 }
142
143 return addressParts.isNotEmpty ? addressParts.join(', ') : 'Location';
144 }
145
147 widget.controller.text = result.name;
148 widget.onLocationSelected(result.latLng);
149 setState(() {
150 _isSearching = false;
151 _searchResults = [];
152 });
153 _focusNode.unfocus();
154 }
155
157 widget.controller.clear();
158 setState(() {
159 _isSearching = false;
160 _searchResults = [];
161 });
162 _focusNode.unfocus();
163 }
164
166 _clearSearch();
167 if (widget.onClose != null) {
168 widget.onClose!();
169 }
170 }
171
172 @override
173 Widget build(BuildContext context) {
174 return Column(
175 children: [
176 Container(
177 decoration: BoxDecoration(
178 color: Colors.white,
179 borderRadius: BorderRadius.circular(12.r),
180 boxShadow: [
181 BoxShadow(
182 color: Colors.black.withValues(alpha: 0.1),
183 blurRadius: 8,
184 offset: const Offset(0, 2),
185 ),
186 ],
187 ),
188 child: Row(
189 children: [
190 Expanded(
191 child: TextField(
192 controller: widget.controller,
193 focusNode: _focusNode,
194 onChanged: _onSearchChanged,
195 autofocus: true,
196 decoration: InputDecoration(
197 hintText: 'scheduling.search_for_location'.tr(),
198 hintStyle: TextStyle(
199 color: Colors.grey[500],
200 fontSize: 14.sp,
201 ),
202 prefixIcon: Icon(
203 FluentIcons.search_16_regular,
204 color: Colors.grey[500],
205 size: 17.sp,
206 ),
207 border: InputBorder.none,
208 contentPadding: EdgeInsets.symmetric(
209 horizontal: 16.w,
210 vertical: 16.h,
211 ),
212 ),
213 style: TextStyle(
214 fontSize: 14.sp,
215 color: Colors.black87,
216 ),
217 ),
218 ),
219 IconButton(
220 icon: Icon(
221 FluentIcons.dismiss_16_filled,
222 color: Colors.grey[600],
223 size: 17.sp,
224 ),
226 ),
227 ],
228 ),
229 ),
230 if (_isSearching || _searchResults.isNotEmpty || _isSearchError)
231 Container(
232 margin: EdgeInsets.only(top: 8.h),
233 decoration: BoxDecoration(
234 color: Colors.white,
235 borderRadius: BorderRadius.circular(12.r),
236 boxShadow: [
237 BoxShadow(
238 color: Colors.black.withValues(alpha: 0.1),
239 blurRadius: 8,
240 offset: const Offset(0, 2),
241 ),
242 ],
243 ),
245 ),
246 ],
247 );
248 }
249
251 if (_isSearching) {
252 return Padding(
253 padding: EdgeInsets.all(16.w),
254 child: Row(
255 children: [
256 SizedBox(
257 width: 20.w,
258 height: 20.h,
259 child: CircularProgressIndicator(
260 strokeWidth: 2,
261 valueColor: AlwaysStoppedAnimation<Color>(ColorPalette.lightGreen),
262 ),
263 ),
264 SizedBox(width: 12.w),
265 Text(
266 'scheduling.searching'.tr(),
267 style: TextStyle(
268 fontSize: 14.sp,
269 color: Colors.grey[600],
270 ),
271 ),
272 ],
273 ),
274 );
275 }
276
277 if (_isSearchError) {
278 return Padding(
279 padding: EdgeInsets.all(16.w),
280 child: Row(
281 children: [
282 Icon(
283 FluentIcons.error_circle_16_filled,
284 color: Colors.red[400],
285 size: 17.sp,
286 ),
287 SizedBox(width: 12.w),
288 Expanded(
289 child: Text(
290 'scheduling.no_locations_found'.tr(),
291 style: TextStyle(
292 fontSize: 14.sp,
293 color: Colors.grey[600],
294 ),
295 ),
296 ),
297 ],
298 ),
299 );
300 }
301
302 if (_searchResults.isNotEmpty) {
303 return ListView.separated(
304 shrinkWrap: true,
305 physics: const NeverScrollableScrollPhysics(),
306 itemCount: _searchResults.length,
307 separatorBuilder: (context, index) => Divider(
308 height: 1,
309 color: Colors.grey[200],
310 ),
311 itemBuilder: (context, index) {
312 final result = _searchResults[index];
313 return ListTile(
314 leading: Icon(
315 FluentIcons.location_16_filled,
316 color: ColorPalette.lightGreen,
317 size: 17.sp,
318 ),
319 title: Text(
320 result.name,
321 style: TextStyle(
322 fontSize: 14.sp,
323 fontWeight: FontWeight.w500,
324 color: Colors.black87,
325 ),
326 ),
327 subtitle: Text(
328 result.address,
329 style: TextStyle(
330 fontSize: 13.sp,
331 color: Colors.grey[600],
332 ),
333 ),
334 onTap: () => _onResultSelected(result),
335 );
336 },
337 );
338 }
339
340 return const SizedBox.shrink();
341 }
342
343 @override
344 void dispose() {
345 _focusNode.dispose();
346 _debounceTimer?.cancel();
347 super.dispose();
348 }
349}
350
override void dispose()
class App extends StatefulWidget build(BuildContext context)
Definition app.dart:31
static const lightGreen
final LatLng latLng
final String name
final VoidCallback onClose
final TextEditingController controller
final Function(LatLng) onLocationSelected
const LocationSearchBar({ super.key, required this.controller, required this.onLocationSelected, this.isVisible=true, this.onClose, })
override State< LocationSearchBar > createState()
final Widget child
final EdgeInsets padding
class Partner String
final String address
final Color color
Definition failures.dart:1
void _closeSearch()
void _clearSearch()
List< SearchResult > _searchResults
class LocationSearchBar extends StatefulWidget _isSearching
Future< void > _performGeocodingSearch(String query) async
void _onSearchChanged(String query)
final FocusNode _focusNode
bool _isSearchError
String _buildAddressString(Placemark placemark)
Widget _buildSearchContent()
void _onResultSelected(SearchResult result)
class UpdateLocationEvent extends LocationSelectionEvent location
final VoidCallback onPressed
style Text( '${ 'scheduling.reference'.tr()}:${collection.internalCode}', style:Theme.of(context).textTheme.bodySmall,)
style SizedBox(height:2.h)
style Column(crossAxisAlignment:CrossAxisAlignment.end, children:[Container(padding:EdgeInsets.symmetric(horizontal:8.w, vertical:4.h), decoration:BoxDecoration(color:ColorPalette.tiffanyBlue.withValues(alpha:0.1), borderRadius:BorderRadius.circular(12),), child:Text(collection.type ?? '', style:Theme.of(context).textTheme.bodySmall?.copyWith(color:ColorPalette.tiffanyBlue, fontWeight:FontWeight.bold,),),),],)