generated from me/template-mit
98 lines
3.3 KiB
Kotlin
98 lines
3.3 KiB
Kotlin
package com.hyperling.weather
|
|
|
|
import io.ktor.client.HttpClient
|
|
import io.ktor.client.call.body
|
|
import io.ktor.client.engine.HttpClientEngine
|
|
import io.ktor.client.plugins.contentnegotiation.ContentNegotiation
|
|
import io.ktor.http.encodeURLParameter
|
|
import io.ktor.serialization.kotlinx.json.json
|
|
import kotlinx.serialization.json.Json
|
|
|
|
/**
|
|
* Repository for fetching weather data from OpenMeteo API.
|
|
* OpenMeteo is free and does not require an API key for basic usage.
|
|
*/
|
|
object WeatherRepository {
|
|
|
|
private const val OPEN_METEO_URL = "https://api.open-meteo.com/v1/forecast"
|
|
|
|
private val json = Json {
|
|
ignoreUnknownKeys = true
|
|
prettyPrint = false
|
|
}
|
|
|
|
private lateinit var httpClient: HttpClient
|
|
|
|
fun init(engine: HttpClientEngine) {
|
|
httpClient = HttpClient(engine) {
|
|
install(ContentNegotiation) {
|
|
json(json)
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Fetch weather data for a location by coordinates.
|
|
* Returns the parsed OpenMeteo daily forecast response.
|
|
*/
|
|
suspend fun fetchWeather(latitude: Double, longitude: Double): OpenMeteoResponse {
|
|
return httpClient.get(OPEN_METEO_URL) {
|
|
url {
|
|
parameters.append("latitude", latitude.toString())
|
|
parameters.append("longitude", longitude.toString())
|
|
parameters.append("daily", "temperature_2m_max,temperature_2m_min")
|
|
parameters.append("forecast_days", "7")
|
|
parameters.append("timezone", "auto")
|
|
}
|
|
}.body()
|
|
}
|
|
|
|
/**
|
|
* Parse the raw API response into our domain models.
|
|
*/
|
|
fun parseDailyWeather(response: OpenMeteoResponse): List<DailyWeather> {
|
|
val dates = response.daily.time ?: emptyList()
|
|
val highs = response.daily.temperature_2m_max ?: emptyList()
|
|
val lows = response.daily.temperature_2m_min ?: emptyList()
|
|
|
|
return dates.mapIndexed { index, date ->
|
|
DailyWeather(
|
|
date = date,
|
|
tempMaxCelsius = if (index < highs.size) highs[index] else null,
|
|
tempMinCelsius = if (index < lows.size) lows[index] else null,
|
|
)
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Convert a location entry to weather data.
|
|
*/
|
|
suspend fun getWeatherForLocation(location: LocationEntry): Result<LocationWeather> {
|
|
return try {
|
|
val response = if (location.zipCode.isNotBlank()) {
|
|
// OpenMeteo doesn't support zip code lookup directly,
|
|
// but we can use the lat/long which should be populated
|
|
fetchWeather(location.latitude, location.longitude)
|
|
} else {
|
|
fetchWeather(location.latitude, location.longitude)
|
|
}
|
|
|
|
val daily = parseDailyWeather(response)
|
|
|
|
Result.success(
|
|
LocationWeather(
|
|
locationId = location.id,
|
|
zipCode = location.zipCode,
|
|
nickname = location.nickname,
|
|
latitude = location.latitude,
|
|
longitude = location.longitude,
|
|
dailyWeather = daily,
|
|
lastUpdatedMillis = System.currentTimeMillis(),
|
|
)
|
|
)
|
|
} catch (e: Exception) {
|
|
Result.failure(e)
|
|
}
|
|
}
|
|
}
|