164 lines
4.9 KiB
Dart
164 lines
4.9 KiB
Dart
import 'dart:convert';
|
|
|
|
import 'package:drift/drift.dart';
|
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
|
import '../../../core/api/api_client.dart';
|
|
import '../../../core/database/app_database.dart';
|
|
|
|
/// A POI parsed from the API response.
|
|
class PlaceData {
|
|
final int osmId;
|
|
final String osmType;
|
|
final String name;
|
|
final String category;
|
|
final double latitude;
|
|
final double longitude;
|
|
final Map<String, dynamic>? address;
|
|
final String? openingHours;
|
|
final Map<String, dynamic>? openingHoursParsed;
|
|
final String? phone;
|
|
final String? website;
|
|
final String? wheelchair;
|
|
final Map<String, dynamic>? tags;
|
|
|
|
PlaceData({
|
|
required this.osmId,
|
|
required this.osmType,
|
|
required this.name,
|
|
required this.category,
|
|
required this.latitude,
|
|
required this.longitude,
|
|
this.address,
|
|
this.openingHours,
|
|
this.openingHoursParsed,
|
|
this.phone,
|
|
this.website,
|
|
this.wheelchair,
|
|
this.tags,
|
|
});
|
|
|
|
String get displayAddress {
|
|
if (address == null) return '';
|
|
final parts = <String>[];
|
|
final street = address!['street'] as String?;
|
|
final housenumber = address!['housenumber'] as String?;
|
|
if (street != null) {
|
|
parts.add(housenumber != null ? '$street $housenumber' : street);
|
|
}
|
|
final postcode = address!['postcode'] as String?;
|
|
final city = address!['city'] as String?;
|
|
if (postcode != null && city != null) {
|
|
parts.add('$postcode $city');
|
|
} else if (city != null) {
|
|
parts.add(city);
|
|
}
|
|
return parts.join(', ');
|
|
}
|
|
|
|
factory PlaceData.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 PlaceData(
|
|
osmId: props['osm_id'] as int,
|
|
osmType: props['osm_type'] as String,
|
|
name: props['name'] as String? ?? '',
|
|
category: props['category'] as String? ?? 'unknown',
|
|
longitude: (coords[0] as num).toDouble(),
|
|
latitude: (coords[1] as num).toDouble(),
|
|
address: props['address'] as Map<String, dynamic>?,
|
|
openingHours: props['opening_hours'] as String?,
|
|
openingHoursParsed:
|
|
props['opening_hours_parsed'] as Map<String, dynamic>?,
|
|
phone: props['phone'] as String?,
|
|
website: props['website'] as String?,
|
|
wheelchair: props['wheelchair'] as String?,
|
|
tags: props['tags'] as Map<String, dynamic>?,
|
|
);
|
|
}
|
|
|
|
/// Parse a single Feature response (GET /api/pois/{osm_type}/{osm_id}).
|
|
factory PlaceData.fromGeoJsonSingle(Map<String, dynamic> data) {
|
|
return PlaceData.fromGeoJsonFeature(data);
|
|
}
|
|
}
|
|
|
|
class PlacesRepository {
|
|
final ApiClient _apiClient;
|
|
final AppDatabase _db;
|
|
|
|
PlacesRepository(this._apiClient, this._db);
|
|
|
|
/// Get POIs in a bounding box via GET /api/pois?bbox=...
|
|
Future<List<PlaceData>> getPoisInBbox({
|
|
required double minLon,
|
|
required double minLat,
|
|
required double maxLon,
|
|
required double maxLat,
|
|
String? category,
|
|
int limit = 100,
|
|
}) async {
|
|
final params = <String, dynamic>{
|
|
'bbox': '$minLon,$minLat,$maxLon,$maxLat',
|
|
'limit': limit,
|
|
};
|
|
if (category != null) {
|
|
params['category'] = category;
|
|
}
|
|
|
|
final data = await _apiClient.get('/api/pois', queryParameters: params);
|
|
final features = (data['features'] as List?) ?? [];
|
|
return features
|
|
.map((f) => PlaceData.fromGeoJsonFeature(f as Map<String, dynamic>))
|
|
.toList();
|
|
}
|
|
|
|
/// Get a single POI by type and ID via GET /api/pois/{osm_type}/{osm_id}.
|
|
Future<PlaceData> getPoiDetail(String osmType, int osmId) async {
|
|
final data = await _apiClient.get('/api/pois/$osmType/$osmId');
|
|
return PlaceData.fromGeoJsonSingle(data as Map<String, dynamic>);
|
|
}
|
|
|
|
// -----------------------------------------------------------------------
|
|
// Favorites (local Drift DB)
|
|
// -----------------------------------------------------------------------
|
|
|
|
Stream<List<Favorite>> watchFavorites() {
|
|
return _db.watchAllFavorites();
|
|
}
|
|
|
|
Future<List<Favorite>> getAllFavorites() {
|
|
return _db.getAllFavorites();
|
|
}
|
|
|
|
Future<int> addFavorite(PlaceData place) {
|
|
return _db.addFavorite(FavoritesCompanion.insert(
|
|
osmId: Value(place.osmId),
|
|
osmType: Value(place.osmType),
|
|
name: place.name,
|
|
latitude: place.latitude,
|
|
longitude: place.longitude,
|
|
addressJson: Value(
|
|
place.address != null ? jsonEncode(place.address) : null,
|
|
),
|
|
createdAt: DateTime.now().millisecondsSinceEpoch ~/ 1000,
|
|
updatedAt: DateTime.now().millisecondsSinceEpoch ~/ 1000,
|
|
));
|
|
}
|
|
|
|
Future<void> removeFavorite(int id) {
|
|
return _db.deleteFavorite(id);
|
|
}
|
|
|
|
Future<Favorite?> isFavorited(String osmType, int osmId) {
|
|
return _db.findFavoriteByOsm(osmType, osmId);
|
|
}
|
|
}
|
|
|
|
final placesRepositoryProvider = Provider<PlacesRepository>((ref) {
|
|
return PlacesRepository(
|
|
ref.watch(apiClientProvider),
|
|
ref.watch(appDatabaseProvider),
|
|
);
|
|
});
|