import 'dart:convert'; import 'package:http/http.dart' as http; import 'package:latlong2/latlong.dart'; import '../models/place_models.dart'; class MapflowApi { MapflowApi({ http.Client? client, String endpoint = const String.fromEnvironment( 'API_BASE_URL', defaultValue: '/graphql', ), }) : _client = client ?? http.Client(), _endpoint = Uri.base.resolve(endpoint); final http.Client _client; final Uri _endpoint; Future> fetchPlaces() async { final data = await _graphql(''' query Places { places { id googlePlaceId name latitude longitude experiences { id status analysis createdAt } } } '''); final places = data['places'] as List; return places.map((item) { final place = item as Map; return PlaceRecommendation( id: place['id'] as String, name: place['name'] as String, area: '', photoUrls: const [], coordinate: LatLng( (place['latitude'] as num).toDouble(), (place['longitude'] as num).toDouble(), ), traits: _traitsFromExperiences(place['experiences'] as List), ); }).toList(); } Future createVoiceExperience({ required String googlePlaceId, required String googleName, required LatLng coordinate, required int durationSeconds, required String audioObjectKey, }) async { await _graphql( ''' mutation CreateVoiceExperience(\$input: CreateVoiceExperienceInput!) { createVoiceExperience(input: \$input) { id } } ''', variables: { 'input': { 'googlePlaceId': googlePlaceId, 'googleName': googleName, 'latitude': coordinate.latitude, 'longitude': coordinate.longitude, 'durationSeconds': durationSeconds, 'audioObjectKey': audioObjectKey, }, }, ); } Future> _graphql( String query, { Map? variables, }) async { final response = await _client.post( _endpoint, headers: const {'content-type': 'application/json'}, body: jsonEncode({'query': query, 'variables': variables ?? {}}), ); if (response.statusCode < 200 || response.statusCode >= 300) { throw StateError('GraphQL request failed with ${response.statusCode}.'); } final payload = jsonDecode(response.body) as Map; final errors = payload['errors']; if (errors is List && errors.isNotEmpty) { throw StateError(jsonEncode(errors)); } return payload['data'] as Map; } Set _traitsFromExperiences(List experiences) { final traits = {}; for (final item in experiences) { final experience = item as Map; final analysis = experience['analysis']; if (analysis is! Map) { continue; } final tags = analysis['tags']; if (tags is! List) { continue; } for (final tag in tags) { final trait = _traitByTag(tag.toString()); if (trait != null) { traits.add(trait); } } } return traits; } PlaceTrait? _traitByTag(String tag) { final name = tag.contains(':') ? tag.split(':').last : tag; for (final trait in PlaceTrait.values) { if (trait.name == name) { return trait; } } return null; } }