136 lines
3.7 KiB
Dart
136 lines
3.7 KiB
Dart
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
|
import '../../../core/api/api_client.dart';
|
|
import '../../../core/database/app_database.dart';
|
|
|
|
/// A single search result parsed from GeoJSON.
|
|
class SearchResult {
|
|
final int osmId;
|
|
final String osmType;
|
|
final String name;
|
|
final String? street;
|
|
final String? housenumber;
|
|
final String? postcode;
|
|
final String? city;
|
|
final String? state;
|
|
final String? country;
|
|
final String? countryCode;
|
|
final String type;
|
|
final double latitude;
|
|
final double longitude;
|
|
final List<double>? extent;
|
|
|
|
SearchResult({
|
|
required this.osmId,
|
|
required this.osmType,
|
|
required this.name,
|
|
this.street,
|
|
this.housenumber,
|
|
this.postcode,
|
|
this.city,
|
|
this.state,
|
|
this.country,
|
|
this.countryCode,
|
|
required this.type,
|
|
required this.latitude,
|
|
required this.longitude,
|
|
this.extent,
|
|
});
|
|
|
|
String get displayAddress {
|
|
final parts = <String>[];
|
|
if (street != null) {
|
|
parts.add(housenumber != null ? '$street $housenumber' : street!);
|
|
}
|
|
if (city != null) parts.add(city!);
|
|
if (country != null) parts.add(country!);
|
|
return parts.join(', ');
|
|
}
|
|
|
|
factory SearchResult.fromGeoJsonFeature(Map<String, dynamic> feature) {
|
|
final geometry = feature['geometry'] as Map<String, dynamic>;
|
|
final coords = geometry['coordinates'] as List;
|
|
final props = feature['properties'] as Map<String, dynamic>;
|
|
|
|
return SearchResult(
|
|
osmId: props['osm_id'] as int,
|
|
osmType: props['osm_type'] as String,
|
|
name: props['name'] as String? ?? '',
|
|
street: props['street'] as String?,
|
|
housenumber: props['housenumber'] as String?,
|
|
postcode: props['postcode'] as String?,
|
|
city: props['city'] as String?,
|
|
state: props['state'] as String?,
|
|
country: props['country'] as String?,
|
|
countryCode: props['country_code'] as String?,
|
|
type: props['type'] as String? ?? 'unknown',
|
|
longitude: (coords[0] as num).toDouble(),
|
|
latitude: (coords[1] as num).toDouble(),
|
|
extent: (props['extent'] as List?)
|
|
?.map((e) => (e as num).toDouble())
|
|
.toList(),
|
|
);
|
|
}
|
|
}
|
|
|
|
class SearchRepository {
|
|
final ApiClient _apiClient;
|
|
final AppDatabase _db;
|
|
|
|
SearchRepository(this._apiClient, this._db);
|
|
|
|
/// Forward search via GET /api/search.
|
|
Future<List<SearchResult>> search(
|
|
String query, {
|
|
double? lat,
|
|
double? lon,
|
|
int limit = 10,
|
|
}) async {
|
|
final params = <String, dynamic>{
|
|
'q': query,
|
|
'limit': limit,
|
|
};
|
|
if (lat != null && lon != null) {
|
|
params['lat'] = lat;
|
|
params['lon'] = lon;
|
|
}
|
|
|
|
final data = await _apiClient.get('/api/search', queryParameters: params);
|
|
final features = (data['features'] as List?) ?? [];
|
|
return features
|
|
.map((f) =>
|
|
SearchResult.fromGeoJsonFeature(f as Map<String, dynamic>))
|
|
.toList();
|
|
}
|
|
|
|
/// Save a query to search history.
|
|
Future<void> saveToHistory(String query, {double? lat, double? lon}) {
|
|
return _db.addSearch(query, lat: lat, lon: lon);
|
|
}
|
|
|
|
/// Get recent search history.
|
|
Future<List<SearchHistoryData>> getRecentSearches() {
|
|
return _db.getRecentSearches();
|
|
}
|
|
|
|
/// Watch recent search history.
|
|
Stream<List<SearchHistoryData>> watchRecentSearches() {
|
|
return _db.watchRecentSearches();
|
|
}
|
|
|
|
/// Delete a single search history entry.
|
|
Future<void> deleteHistoryEntry(int id) {
|
|
return _db.deleteSearchEntry(id);
|
|
}
|
|
|
|
/// Clear all search history.
|
|
Future<void> clearHistory() {
|
|
return _db.clearSearchHistory();
|
|
}
|
|
}
|
|
|
|
final searchRepositoryProvider = Provider<SearchRepository>((ref) {
|
|
return SearchRepository(
|
|
ref.watch(apiClientProvider),
|
|
ref.watch(appDatabaseProvider),
|
|
);
|
|
});
|