112 lines
3.3 KiB
Dart
112 lines
3.3 KiB
Dart
import 'dart:convert';
|
|
import 'dart:math';
|
|
|
|
import 'package:http/http.dart' as http;
|
|
|
|
/// Fetches public web API data used to build question text and answers.
|
|
class ExternalDataFetcher {
|
|
ExternalDataFetcher({http.Client? client}) : _client = client ?? http.Client();
|
|
|
|
final http.Client _client;
|
|
final Random _random = Random();
|
|
|
|
/// Random country with population (millions) and capital for trivia questions.
|
|
Future<CountryFacts?> fetchRandomCountry() async {
|
|
final http.Response response = await _client.get(
|
|
Uri.parse(
|
|
'https://restcountries.com/v3.1/all?fields=name,population,capital',
|
|
),
|
|
);
|
|
if (response.statusCode != 200) {
|
|
return null;
|
|
}
|
|
final List<dynamic> countries =
|
|
jsonDecode(response.body) as List<dynamic>;
|
|
if (countries.isEmpty) {
|
|
return null;
|
|
}
|
|
|
|
for (int attempt = 0; attempt < 8; attempt++) {
|
|
final Map<String, dynamic> raw =
|
|
countries[_random.nextInt(countries.length)] as Map<String, dynamic>;
|
|
final int? population = raw['population'] as int?;
|
|
final List<dynamic>? capitals = raw['capital'] as List<dynamic>?;
|
|
final Map<String, dynamic>? nameMap =
|
|
raw['name'] as Map<String, dynamic>?;
|
|
final String? commonName = nameMap?['common'] as String?;
|
|
if (population == null ||
|
|
population < 500_000 ||
|
|
capitals == null ||
|
|
capitals.isEmpty ||
|
|
commonName == null) {
|
|
continue;
|
|
}
|
|
return CountryFacts(
|
|
name: commonName,
|
|
populationMillions: (population / 1_000_000).round(),
|
|
capital: capitals.first as String,
|
|
);
|
|
}
|
|
return null;
|
|
}
|
|
|
|
/// Current temperature (°C, rounded) for a European city via Open-Meteo.
|
|
Future<WeatherFacts?> fetchRandomCityWeather() async {
|
|
const List<({String city, double lat, double lon})> cities =
|
|
<({String city, double lat, double lon})>[
|
|
(city: 'Berlin', lat: 52.52, lon: 13.41),
|
|
(city: 'Paris', lat: 48.86, lon: 2.35),
|
|
(city: 'Madrid', lat: 40.42, lon: -3.70),
|
|
(city: 'Rome', lat: 41.90, lon: 12.50),
|
|
(city: 'Amsterdam', lat: 52.37, lon: 4.90),
|
|
];
|
|
final ({String city, double lat, double lon}) pick =
|
|
cities[_random.nextInt(cities.length)];
|
|
final Uri uri = Uri.parse(
|
|
'https://api.open-meteo.com/v1/forecast'
|
|
'?latitude=${pick.lat}&longitude=${pick.lon}'
|
|
'¤t=temperature_2m&timezone=auto',
|
|
);
|
|
final http.Response response = await _client.get(uri);
|
|
if (response.statusCode != 200) {
|
|
return null;
|
|
}
|
|
final Map<String, dynamic> json =
|
|
jsonDecode(response.body) as Map<String, dynamic>;
|
|
final Map<String, dynamic>? current =
|
|
json['current'] as Map<String, dynamic>?;
|
|
final num? temp = current?['temperature_2m'] as num?;
|
|
if (temp == null) {
|
|
return null;
|
|
}
|
|
return WeatherFacts(
|
|
city: pick.city,
|
|
temperatureCelsius: temp.round(),
|
|
);
|
|
}
|
|
|
|
void close() => _client.close();
|
|
}
|
|
|
|
class CountryFacts {
|
|
CountryFacts({
|
|
required this.name,
|
|
required this.populationMillions,
|
|
required this.capital,
|
|
});
|
|
|
|
final String name;
|
|
final int populationMillions;
|
|
final String capital;
|
|
}
|
|
|
|
class WeatherFacts {
|
|
WeatherFacts({
|
|
required this.city,
|
|
required this.temperatureCelsius,
|
|
});
|
|
|
|
final String city;
|
|
final int temperatureCelsius;
|
|
}
|