201 lines
7.2 KiB
Dart
201 lines
7.2 KiB
Dart
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<RoadAlertCategory> 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<RoadAlertProviderStatus> 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<List<RoadAlert>> 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<String, dynamic> jsonMap = parsed is Map<String, dynamic> ? 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<String, dynamic> jsonMap = parsed is Map<String, dynamic> ? parsed : {'alerts': parsed};
|
|
return CommunityTrafficParser.parseAlertsJson(
|
|
jsonMap,
|
|
providerId: id,
|
|
providerName: name,
|
|
);
|
|
}
|
|
}
|
|
|
|
return [];
|
|
} catch (_) {
|
|
return [];
|
|
}
|
|
}
|
|
}
|