maps/mobile/lib/features/search/presentation/screens/search_screen.dart
2026-03-30 09:22:16 +02:00

170 lines
4.8 KiB
Dart

import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:go_router/go_router.dart';
import 'package:latlong2/latlong.dart';
import '../../../map/providers/map_provider.dart';
import '../../data/search_repository.dart';
import '../../providers/search_provider.dart';
import '../widgets/search_result_tile.dart';
class SearchScreen extends ConsumerStatefulWidget {
const SearchScreen({super.key});
@override
ConsumerState<SearchScreen> createState() => _SearchScreenState();
}
class _SearchScreenState extends ConsumerState<SearchScreen> {
late final TextEditingController _controller;
@override
void initState() {
super.initState();
_controller = TextEditingController();
}
@override
void dispose() {
_controller.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
final searchState = ref.watch(searchProvider);
return Scaffold(
appBar: AppBar(
title: TextField(
controller: _controller,
autofocus: true,
decoration: const InputDecoration(
hintText: 'Search places...',
border: InputBorder.none,
filled: false,
),
onChanged: (value) {
ref.read(searchProvider.notifier).updateQuery(value);
},
),
actions: [
if (_controller.text.isNotEmpty)
IconButton(
icon: const Icon(Icons.clear),
onPressed: () {
_controller.clear();
ref.read(searchProvider.notifier).updateQuery('');
},
),
],
),
body: _buildBody(context, searchState),
);
}
Widget _buildBody(BuildContext context, SearchState searchState) {
if (searchState.error != null) {
return Center(
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
const Icon(Icons.error_outline, size: 48),
const SizedBox(height: 8),
Text(searchState.error!),
],
),
);
}
if (searchState.isLoading) {
return const Center(child: CircularProgressIndicator());
}
if (searchState.query.trim().isNotEmpty) {
if (searchState.results.isEmpty) {
return const Center(child: Text('No results found.'));
}
return ListView.builder(
itemCount: searchState.results.length,
itemBuilder: (context, index) {
final result = searchState.results[index];
return SearchResultTile(
result: result,
onTap: () => _onResultTap(result),
);
},
);
}
if (searchState.recentSearches.isEmpty) {
return const Center(
child: Text('Start typing to search for places.'),
);
}
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Padding(
padding: const EdgeInsets.fromLTRB(16, 16, 16, 8),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
'Recent Searches',
style: Theme.of(context).textTheme.titleSmall,
),
TextButton(
onPressed: () =>
ref.read(searchProvider.notifier).clearHistory(),
child: const Text('Clear all'),
),
],
),
),
Expanded(
child: ListView.builder(
itemCount: searchState.recentSearches.length,
itemBuilder: (context, index) {
final item = searchState.recentSearches[index];
return ListTile(
leading: const Icon(Icons.history),
title: Text(item.query),
trailing: IconButton(
icon: const Icon(Icons.close, size: 18),
onPressed: () => ref
.read(searchProvider.notifier)
.deleteHistoryEntry(item.id),
),
onTap: () {
_controller.text = item.query;
ref.read(searchProvider.notifier).updateQuery(item.query);
},
);
},
),
),
],
);
}
void _onResultTap(SearchResult result) {
ref.read(searchProvider.notifier).selectResult(result);
ref.read(mapProvider.notifier).selectPlace(
SelectedPlace(
name: result.name,
address: result.displayAddress,
category: result.type,
latitude: result.latitude,
longitude: result.longitude,
osmId: result.osmId,
osmType: result.osmType,
),
);
ref.read(mapProvider.notifier).updateCamera(
LatLng(result.latitude, result.longitude),
15,
);
context.go('/');
}
}