From cb44e24ae1d83230b49469df23fcf8a0e5ba97bb Mon Sep 17 00:00:00 2001 From: MM20 <15646950+MM2-0@users.noreply.github.com> Date: Wed, 22 Sep 2021 18:22:54 +0200 Subject: [PATCH] Add Bright Sky (Deutscher Wetterdienst) weather provider --- .../fragment/PreferencesWeatherFragment.kt | 4 + i18n/src/main/res/values-de/strings.xml | 3 + i18n/src/main/res/values/strings.xml | 3 + .../preferences/LauncherPreferences.kt | 3 +- .../mm20/launcher2/weather/WeatherProvider.kt | 2 + .../weather/brightsky/BrightSkyApi.kt | 38 ++++++ .../weather/brightsky/BrightskyProvider.kt | 129 ++++++++++++++++++ 7 files changed, 181 insertions(+), 1 deletion(-) create mode 100644 weather/src/main/java/de/mm20/launcher2/weather/brightsky/BrightSkyApi.kt create mode 100644 weather/src/main/java/de/mm20/launcher2/weather/brightsky/BrightskyProvider.kt diff --git a/app/src/main/java/de/mm20/launcher2/fragment/PreferencesWeatherFragment.kt b/app/src/main/java/de/mm20/launcher2/fragment/PreferencesWeatherFragment.kt index ee60fa3b..a721e5a0 100644 --- a/app/src/main/java/de/mm20/launcher2/fragment/PreferencesWeatherFragment.kt +++ b/app/src/main/java/de/mm20/launcher2/fragment/PreferencesWeatherFragment.kt @@ -16,6 +16,7 @@ import de.mm20.launcher2.preferences.WeatherProviders import de.mm20.launcher2.weather.WeatherLocation import de.mm20.launcher2.weather.WeatherProvider import de.mm20.launcher2.weather.WeatherViewModel +import de.mm20.launcher2.weather.brightsky.BrightskyProvider import de.mm20.launcher2.weather.here.HereProvider import de.mm20.launcher2.weather.metno.MetNoProvider import de.mm20.launcher2.weather.openweathermap.OpenWeatherMapProvider @@ -58,6 +59,9 @@ class PreferencesWeatherFragment : PreferenceFragmentCompat() { MetNoProvider(context).takeIf { it.isAvailable() }?.let { providers.add(WeatherProviders.MET_NO to it.name) } + BrightskyProvider(context).takeIf { it.isAvailable() }?.let { + providers.add(WeatherProviders.BRIGHT_SKY to it.name) + } if (providers.isEmpty()) { providerPref.summary = context.getString( diff --git a/i18n/src/main/res/values-de/strings.xml b/i18n/src/main/res/values-de/strings.xml index bc1b6759..94cfc2e8 100644 --- a/i18n/src/main/res/values-de/strings.xml +++ b/i18n/src/main/res/values-de/strings.xml @@ -154,6 +154,7 @@ In dieser URL fehlt der Platzhalter „${1}“ Wetterdienst OpenWeatherMap + Deutscher Wetterdienst (nur Deutschland) Von diesem Wetterdienst nicht unterstützt EE, d. MMMM %1$s • %2$s @@ -253,6 +254,8 @@ Bedeckt Leichter Regen und Gewitter Schneefall + Hagel + Gewitter Starke Schneeschauer Starke Regenschauer Regenschauer und Gewitter diff --git a/i18n/src/main/res/values/strings.xml b/i18n/src/main/res/values/strings.xml index 5abb34be..8e3860d7 100644 --- a/i18n/src/main/res/values/strings.xml +++ b/i18n/src/main/res/values/strings.xml @@ -193,6 +193,7 @@ Provider MET Norway OpenWeatherMap + Deutscher Wetterdienst (Germany only) HERE Not supported by this provider EE, MMMM d @@ -406,6 +407,8 @@ Cloudy Light rain and thunder Snow + Hail + Thunderstorm Heavy snow showers Heavy rain showers Rain showers and thunder diff --git a/preferences/src/main/java/de/mm20/launcher2/preferences/LauncherPreferences.kt b/preferences/src/main/java/de/mm20/launcher2/preferences/LauncherPreferences.kt index 37139371..b5923107 100644 --- a/preferences/src/main/java/de/mm20/launcher2/preferences/LauncherPreferences.kt +++ b/preferences/src/main/java/de/mm20/launcher2/preferences/LauncherPreferences.kt @@ -159,7 +159,8 @@ enum class IconShape(override val value: String) : PreferenceEnum { enum class WeatherProviders(override val value: String) : PreferenceEnum { OPENWEATHERMAP("0"), HERE("3"), - MET_NO("2"); + MET_NO("2"), + BRIGHT_SKY("4"); companion object { fun byValue(value: String): WeatherProviders { diff --git a/weather/src/main/java/de/mm20/launcher2/weather/WeatherProvider.kt b/weather/src/main/java/de/mm20/launcher2/weather/WeatherProvider.kt index 4ca0ec62..8d4d48ae 100644 --- a/weather/src/main/java/de/mm20/launcher2/weather/WeatherProvider.kt +++ b/weather/src/main/java/de/mm20/launcher2/weather/WeatherProvider.kt @@ -9,6 +9,7 @@ import androidx.core.content.getSystemService import de.mm20.launcher2.ktx.checkPermission import de.mm20.launcher2.preferences.LauncherPreferences import de.mm20.launcher2.preferences.WeatherProviders +import de.mm20.launcher2.weather.brightsky.BrightskyProvider import de.mm20.launcher2.weather.here.HereProvider import de.mm20.launcher2.weather.metno.MetNoProvider import de.mm20.launcher2.weather.openweathermap.OpenWeatherMapProvider @@ -103,6 +104,7 @@ abstract class WeatherProvider { return when (LauncherPreferences.instance.weatherProvider) { WeatherProviders.OPENWEATHERMAP -> OpenWeatherMapProvider(context) WeatherProviders.HERE -> HereProvider(context) + WeatherProviders.BRIGHT_SKY -> BrightskyProvider(context) else -> MetNoProvider(context) }.takeIf { it.isAvailable() } } diff --git a/weather/src/main/java/de/mm20/launcher2/weather/brightsky/BrightSkyApi.kt b/weather/src/main/java/de/mm20/launcher2/weather/brightsky/BrightSkyApi.kt new file mode 100644 index 00000000..0dc7612e --- /dev/null +++ b/weather/src/main/java/de/mm20/launcher2/weather/brightsky/BrightSkyApi.kt @@ -0,0 +1,38 @@ +package de.mm20.launcher2.weather.brightsky + +import com.google.gson.annotations.SerializedName +import retrofit2.http.GET +import retrofit2.http.Query + +data class BrightSkyResult( + val weather: Array +) + +data class BrightSkyResultWether( + val timestamp: String?, + @SerializedName("source_id") val sourceId: Int?, + @SerializedName("cloud_cover") val cloudCover: Double?, + val condition: String?, + @SerializedName("dew_point") val dewPoint: Double?, + val icon: String?, + val precipitation: Double?, + @SerializedName("pressure_msl") val pressureMsl: Double?, + @SerializedName("relative_humidity") val relativeHumidity: Double?, + val sunshine: Double?, + val temperature: Double?, + val visibility: Double?, + @SerializedName("wind_direction") val windDirection: Double?, + @SerializedName("wind_speed") val windSpeed: Double?, + @SerializedName("wind_gust_direction") val windGustDirection: Double?, + @SerializedName("wind_gust_speed") val windGustSpeed: Double?, +) + +interface BrightSkyApi { + @GET("/weather?units=si") + suspend fun weather( + @Query("date") date: String, + @Query("last_date") lastDate: String, + @Query("lat") lat: Double, + @Query("lon") lon: Double, + ): BrightSkyResult +} \ No newline at end of file diff --git a/weather/src/main/java/de/mm20/launcher2/weather/brightsky/BrightskyProvider.kt b/weather/src/main/java/de/mm20/launcher2/weather/brightsky/BrightskyProvider.kt new file mode 100644 index 00000000..a577bf96 --- /dev/null +++ b/weather/src/main/java/de/mm20/launcher2/weather/brightsky/BrightskyProvider.kt @@ -0,0 +1,129 @@ +package de.mm20.launcher2.weather.brightsky + +import android.content.Context +import android.content.SharedPreferences +import android.icu.text.SimpleDateFormat +import android.icu.util.Calendar +import de.mm20.launcher2.crashreporter.CrashReporter +import de.mm20.launcher2.weather.* +import retrofit2.Retrofit +import retrofit2.converter.gson.GsonConverterFactory +import retrofit2.create +import java.lang.Exception +import kotlin.math.roundToInt + +class BrightskyProvider( + override val context: Context +) : LatLonWeatherProvider() { + + + val apiClient by lazy { + val retrofit = Retrofit.Builder() + .baseUrl("https://api.brightsky.dev/") + .addConverterFactory(GsonConverterFactory.create()) + .build() + retrofit.create() + } + + override suspend fun loadWeatherData(location: LatLonWeatherLocation): WeatherUpdateResult? { + + val result = runCatching { + val format = SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssZ") + val date = Calendar.getInstance() + date.timeInMillis -= 1000 * 60 * 30 + val startDate = format.format(date.timeInMillis) + date.timeInMillis += 1000 * 60 * 60 * 24 * 14 + val endDate = format.format(date.timeInMillis) + apiClient.weather( + date = startDate, + lastDate = endDate, + lat = location.lat, + lon = location.lon, + ) + }.getOrElse { + CrashReporter.logException(Exception(it)) + return null + } + val format = SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssXXX") + val forecasts = mutableListOf() + val updateTime = System.currentTimeMillis() + for (weather in result.weather) { + forecasts.add( + Forecast( + timestamp = format.parse(weather.timestamp)?.time ?: continue, + clouds = weather.cloudCover?.roundToInt() ?: -1, + condition = getCondition(weather.icon ?: continue) ?: continue, + humidity = weather.relativeHumidity ?: -1.0, + icon = getIcon(weather.icon) ?: continue, + location = location.name, + maxTemp = weather.temperature ?: continue, + minTemp = weather.temperature, + night = (weather.sunshine ?: 100.0).roundToInt() == 0, + pressure = weather.pressureMsl ?: -1.0, + provider = "Deutscher Wetterdienst", + providerUrl = "https://www.dwd.de/", + precipitation = weather.precipitation ?: -1.0, + precipProbability = -1, + temperature = weather.temperature, + updateTime = updateTime, + windDirection = weather.windDirection ?: -1.0, + windSpeed = weather.windSpeed ?: -1.0 + ) + ) + } + return WeatherUpdateResult( + forecasts, location + ) + } + + private fun getIcon(icon: String): Int? { + return when (icon) { + "clear-day", "clear-night" -> Forecast.CLEAR + "partly-cloudy-day", "partly-cloudy-night" -> Forecast.PARTLY_CLOUDY + "cloudy" -> Forecast.CLOUDY + "fog" -> Forecast.FOG + "wind" -> Forecast.WIND + "rain" -> Forecast.SHOWERS + "sleet" -> Forecast.SLEET + "snow" -> Forecast.SNOW + "hail" -> Forecast.HAIL + "thunderstorm" -> Forecast.THUNDERSTORM + else -> null + } + } + + private fun getCondition(icon: String): String? { + val resId = when (icon) { + "clear-day", "clear-night" -> R.string.weather_clearsky + "partly-cloudy-day", "partly-cloudy-night" -> R.string.weather_partlycloudy + "cloudy" -> R.string.weather_cloudy + "fog" -> R.string.weather_fog + "wind" -> R.string.weather_wind + "rain" -> R.string.weather_rain + "sleet" -> R.string.weather_sleet + "snow" -> R.string.weather_snow + "hail" -> R.string.weather_hail + "thunderstorm" -> R.string.weather_thunder + else -> return null + } + return context.getString(resId) + } + + override val preferences: SharedPreferences + get() = context.getSharedPreferences(PREFS, Context.MODE_PRIVATE) + + override fun isUpdateRequired(): Boolean { + return getLastUpdate() + 3600000 < System.currentTimeMillis() + } + + override fun isAvailable(): Boolean { + return true + } + + override val name: String + get() = context.getString(R.string.provider_brightsky) + + companion object { + const val PREFS = "bright_sky" + } +} \ No newline at end of file