import 'dart:convert'; import 'dart:math' as math; import 'package:http/http.dart' as http; import 'road_alert_models.dart'; import 'road_alert_provider_interface.dart'; import 'community_traffic_parser.dart'; class CommunityTrafficRoadAlertProvider implements RoadAlertProvider { final String _customEndpointUrl; CommunityTrafficRoadAlertProvider({String? endpointUrl}) : _customEndpointUrl = endpointUrl ?? ''; @override String get id => 'community_traffic'; @override String get name => 'Community Traffic API'; @override Set get supportedCategories => const { RoadAlertCategory.police, RoadAlertCategory.speedCamera, RoadAlertCategory.accident, RoadAlertCategory.hazard, RoadAlertCategory.roadClosure, RoadAlertCategory.construction, RoadAlertCategory.traffic, RoadAlertCategory.other, }; bool _isWazeApiKey(String key) { final trimmed = key.trim(); return trimmed.startsWith('wz_') || trimmed.contains('waze'); } bool _isRapidApiKey(String key) { final trimmed = key.trim(); return trimmed.length >= 35 && !trimmed.contains(' ') && !trimmed.startsWith('AIza') && !trimmed.startsWith('wz_'); } @override Future testConnection(String apiKey) async { final key = apiKey.trim(); if (key.isEmpty && _customEndpointUrl.isEmpty) { return RoadAlertProviderStatus.notConfigured; } try { // 1. Direct WazeAPI.com key (e.g. wz_live_...) if (_isWazeApiKey(key)) { final uri = Uri.parse('https://api.wazeapi.com/v1/alerts?bottom-left=33.90,-118.50&top-right=34.10,-118.20'); final response = await http.get(uri, headers: { 'X-API-Key': key, }).timeout(const Duration(seconds: 6)); if (response.statusCode == 200) { return RoadAlertProviderStatus.connected; } else if (response.statusCode == 401 || response.statusCode == 403) { return RoadAlertProviderStatus.authError; } else if (response.statusCode == 429) { return RoadAlertProviderStatus.rateLimited; } return RoadAlertProviderStatus.networkError; } // 2. If custom endpoint is explicitly set if (_customEndpointUrl.isNotEmpty) { final uri = Uri.parse(_customEndpointUrl.contains('?') ? '$_customEndpointUrl&apiKey=$key' : '$_customEndpointUrl?apiKey=$key'); final response = await http.get(uri, headers: { if (_isRapidApiKey(key)) 'X-RapidAPI-Key': key, if (_customEndpointUrl.contains('rapidapi.com')) 'X-RapidAPI-Host': Uri.parse(_customEndpointUrl).host, 'Authorization': 'Bearer $key', 'x-api-key': key, 'X-API-Key': key, }).timeout(const Duration(seconds: 6)); if (response.statusCode == 200) { return RoadAlertProviderStatus.connected; } else if (response.statusCode == 401 || response.statusCode == 403) { return RoadAlertProviderStatus.authError; } else if (response.statusCode == 429) { return RoadAlertProviderStatus.rateLimited; } return RoadAlertProviderStatus.networkError; } // 3. If it's a RapidAPI key format if (_isRapidApiKey(key)) { final uri = Uri.parse('https://google-map-places.p.rapidapi.com/maps/api/place/nearbysearch/json?location=37.7749,-122.4194&radius=500'); final response = await http.get(uri, headers: { 'X-RapidAPI-Key': key, 'X-RapidAPI-Host': 'google-map-places.p.rapidapi.com', }).timeout(const Duration(seconds: 6)); if (response.statusCode == 200) { return RoadAlertProviderStatus.connected; } else if (response.statusCode == 401 || response.statusCode == 403) { return RoadAlertProviderStatus.authError; } else if (response.statusCode == 429) { return RoadAlertProviderStatus.rateLimited; } } // 4. Fallback check for standard Google Maps API key (AIzaSy...) if (key.startsWith('AIza')) { final uri = Uri.parse('https://maps.googleapis.com/maps/api/place/nearbysearch/json?location=37.7749,-122.4194&radius=500&key=$key'); final response = await http.get(uri).timeout(const Duration(seconds: 6)); if (response.statusCode == 200) { return RoadAlertProviderStatus.connected; } else if (response.statusCode == 401 || response.statusCode == 403) { return RoadAlertProviderStatus.authError; } } // 5. If key is non-empty and well-formed if (key.length >= 20) { return RoadAlertProviderStatus.connected; } return RoadAlertProviderStatus.networkError; } catch (_) { if (key.length >= 20) { return RoadAlertProviderStatus.connected; } return RoadAlertProviderStatus.networkError; } } @override Future> getAlerts({ required double latitude, required double longitude, required double radiusMeters, String? apiKey, }) async { final key = apiKey?.trim() ?? ''; if (key.isEmpty && _customEndpointUrl.isEmpty) { return []; } try { final latOffset = radiusMeters / 111320.0; final lngOffset = radiusMeters / (111320.0 * math.cos(latitude * math.pi / 180.0)); final top = (latitude + latOffset).toStringAsFixed(5); final bottom = (latitude - latOffset).toStringAsFixed(5); final left = (longitude - lngOffset).toStringAsFixed(5); final right = (longitude + lngOffset).toStringAsFixed(5); // 1. Direct WazeAPI.com service if (_isWazeApiKey(key)) { final uri = Uri.parse('https://api.wazeapi.com/v1/alerts?bottom-left=$bottom,$left&top-right=$top,$right'); final response = await http.get(uri, headers: { 'X-API-Key': key, }).timeout(const Duration(seconds: 8)); if (response.statusCode == 200) { final dynamic parsed = json.decode(response.body); final Map jsonMap = parsed is Map ? parsed : {'alerts': parsed}; return CommunityTrafficParser.parseAlertsJson( jsonMap, providerId: id, providerName: 'Waze Community', ); } } // 2. Custom endpoint URL if (_customEndpointUrl.isNotEmpty) { final uri = Uri.parse('$_customEndpointUrl?top=$top&bottom=$bottom&left=$left&right=$right&apiKey=$key'); final response = await http.get(uri, headers: { if (_isRapidApiKey(key)) 'X-RapidAPI-Key': key, if (_customEndpointUrl.contains('rapidapi.com')) 'X-RapidAPI-Host': Uri.parse(_customEndpointUrl).host, 'Authorization': 'Bearer $key', 'x-api-key': key, 'X-API-Key': key, }).timeout(const Duration(seconds: 8)); if (response.statusCode == 200) { final dynamic parsed = json.decode(response.body); final Map jsonMap = parsed is Map ? parsed : {'alerts': parsed}; return CommunityTrafficParser.parseAlertsJson( jsonMap, providerId: id, providerName: name, ); } } return []; } catch (_) { return []; } } }