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