From a2621bee0b10c945ea2f7f04ba89645b164e2dde Mon Sep 17 00:00:00 2001 From: MM20 <15646950+MM2-0@users.noreply.github.com> Date: Wed, 11 Mar 2026 22:07:21 +0100 Subject: [PATCH] rearrange weather widget, add uv index Close #1630 --- .../ui/component/weather/WeatherIconColors.kt | 2 +- .../launcher/widgets/weather/WeatherWidget.kt | 348 ++++++++++-------- .../base/src/main/res/drawable/cloud_20px.xml | 10 + .../main/res/drawable/humidity_mid_20px.xml | 10 + .../src/main/res/drawable/navigation_20px.xml | 10 + .../base/src/main/res/drawable/sunny_20px.xml | 10 + .../plugin/contracts/WeatherPluginContract.kt | 1 + .../de/mm20/launcher2/database/AppDatabase.kt | 4 +- .../database/entities/ForecastEntity.kt | 2 + .../database/migrations/Migration_31_32.kt | 17 + .../de/mm20/launcher2/weather/Forecast.kt | 5 +- .../weather/breezy/BreezyWeatherProvider.kt | 1 + .../mm20/launcher2/weather/metno/MetNoApi.kt | 15 +- .../launcher2/weather/metno/MetNoProvider.kt | 7 +- .../openweathermap/OpenWeatherMapApi.kt | 10 +- .../weather/plugin/PluginWeatherProvider.kt | 1 + .../de/mm20/launcher2/sdk/weather/Forecast.kt | 5 + .../launcher2/sdk/weather/WeatherProvider.kt | 1 + 18 files changed, 287 insertions(+), 172 deletions(-) create mode 100644 core/base/src/main/res/drawable/cloud_20px.xml create mode 100644 core/base/src/main/res/drawable/humidity_mid_20px.xml create mode 100644 core/base/src/main/res/drawable/navigation_20px.xml create mode 100644 core/base/src/main/res/drawable/sunny_20px.xml create mode 100644 data/database/src/main/java/de/mm20/launcher2/database/migrations/Migration_31_32.kt diff --git a/app/ui/src/main/java/de/mm20/launcher2/ui/component/weather/WeatherIconColors.kt b/app/ui/src/main/java/de/mm20/launcher2/ui/component/weather/WeatherIconColors.kt index bfa0adc13..ffc5f6157 100644 --- a/app/ui/src/main/java/de/mm20/launcher2/ui/component/weather/WeatherIconColors.kt +++ b/app/ui/src/main/java/de/mm20/launcher2/ui/component/weather/WeatherIconColors.kt @@ -65,7 +65,7 @@ object WeatherIconDefaults { @Composable fun themedColors(backgroundColor: Color): WeatherIconColors { - val themeColor = MaterialTheme.colorScheme.primary + val themeColor = MaterialTheme.colorScheme.secondary val neutralColor = MaterialTheme.colorScheme.outline return remember(backgroundColor, themeColor, neutralColor) { diff --git a/app/ui/src/main/java/de/mm20/launcher2/ui/launcher/widgets/weather/WeatherWidget.kt b/app/ui/src/main/java/de/mm20/launcher2/ui/launcher/widgets/weather/WeatherWidget.kt index 05d823e3d..28300052b 100644 --- a/app/ui/src/main/java/de/mm20/launcher2/ui/launcher/widgets/weather/WeatherWidget.kt +++ b/app/ui/src/main/java/de/mm20/launcher2/ui/launcher/widgets/weather/WeatherWidget.kt @@ -3,6 +3,7 @@ package de.mm20.launcher2.ui.launcher.widgets.weather import android.content.ComponentName import android.content.Context import android.content.Intent +import android.content.res.Resources import android.net.Uri import android.text.format.DateUtils import androidx.appcompat.app.AppCompatActivity @@ -21,20 +22,25 @@ import androidx.compose.foundation.interaction.MutableInteractionSource import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.FlowRow +import androidx.compose.foundation.layout.IntrinsicSize import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxHeight import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.offset import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.requiredSize import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.width import androidx.compose.foundation.layout.widthIn import androidx.compose.foundation.layout.wrapContentHeight import androidx.compose.foundation.lazy.LazyRow import androidx.compose.foundation.lazy.itemsIndexed import androidx.compose.foundation.lazy.rememberLazyListState import androidx.compose.foundation.shape.CircleShape -import androidx.compose.foundation.shape.CornerSize import androidx.compose.material3.Button import androidx.compose.material3.ButtonDefaults import androidx.compose.material3.Icon @@ -57,11 +63,13 @@ import androidx.compose.ui.geometry.Offset import androidx.compose.ui.geometry.Rect import androidx.compose.ui.layout.onPlaced import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.platform.LocalResources import androidx.compose.ui.platform.LocalView import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.semantics.contentDescription import androidx.compose.ui.semantics.semantics +import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.IntSize import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp @@ -78,7 +86,6 @@ import de.mm20.launcher2.ui.R import de.mm20.launcher2.ui.common.WeatherLocationSearchDialog import de.mm20.launcher2.ui.component.Banner import de.mm20.launcher2.ui.component.MissingPermissionBanner -import de.mm20.launcher2.ui.component.Tooltip import de.mm20.launcher2.ui.component.weather.AnimatedWeatherIcon import de.mm20.launcher2.ui.component.weather.WeatherIcon import de.mm20.launcher2.ui.component.weather.WeatherIconDefaults @@ -193,7 +200,7 @@ fun WeatherWidget(widget: WeatherWidget) { color = MaterialTheme.colorScheme.surfaceContainer.copy(alpha = MaterialTheme.transparency.surface), modifier = Modifier .fillMaxWidth() - .padding(8.dp), + .padding(start = 8.dp, bottom = 8.dp, end = 8.dp), shape = MaterialTheme.shapes.small, ) { Column( @@ -248,16 +255,13 @@ fun CurrentWeather( ) } - val useFahrenheit = measurementSystem == MeasurementSystem.UnitedStates - val useInches = measurementSystem == MeasurementSystem.UnitedStates - val useMph = - measurementSystem == MeasurementSystem.UnitedStates || measurementSystem == MeasurementSystem.UnitedKingdom - var bounds by remember { mutableStateOf(Rect.Zero) } val view = LocalView.current - Column( + Row( modifier = Modifier + .fillMaxWidth() + .height(IntrinsicSize.Max) .onPlaced { val size = it.size val offset = it.localToRoot(Offset.Zero) @@ -288,128 +292,154 @@ fun CurrentWeather( }, interactionSource = remember { MutableInteractionSource() }, indication = LocalIndication.current, - ) + ), + horizontalArrangement = Arrangement.spacedBy(16.dp), ) { Column( + modifier = Modifier + .weight(1f) + .fillMaxHeight() + .padding( + top = 16.dp, + end = 16.dp, + start = 16.dp, + bottom = 8.dp, + ) ) { - Row( - verticalAlignment = Alignment.Top, - horizontalArrangement = Arrangement.SpaceBetween, - ) { - Row( - modifier = Modifier - .weight(1f) - .padding(top = 16.dp, start = 16.dp, end = 16.dp), - verticalAlignment = Alignment.CenterVertically, - ) { - Text( - text = forecast.location, - style = MaterialTheme.typography.titleMedium + Text( + text = forecast.location, + style = MaterialTheme.typography.titleMedium + ) + + Text( + modifier = Modifier.padding(vertical = 2.dp), + text = formatTemperature( + context, + forecast.temperature.toFloat(), + measurementSystem + ), + style = MaterialTheme.typography.headlineLarge, + ) + + Text( + text = forecast.condition, + style = MaterialTheme.typography.bodySmall, + color = MaterialTheme.colorScheme.secondary, + ) + + Spacer(modifier = Modifier.weight(1f)) + + Text( + text = "${forecast.provider} (${ + formatTime( + LocalContext.current, + forecast.updateTime, + timeFormat, ) - } - Tooltip( - tooltipText = stringResource(R.string.preference_weather_provider) - ) { - Surface( - shape = MaterialTheme.shapes.extraSmall.copy( - topStart = CornerSize(0), - topEnd = CornerSize(0), - bottomEnd = CornerSize(0) - ), - color = MaterialTheme.colorScheme.secondaryContainer.copy(alpha = MaterialTheme.transparency.surface), - ) { - Text( - text = "${forecast.provider} (${ - formatTime( - LocalContext.current, - forecast.updateTime, - timeFormat, - ) - })", - style = MaterialTheme.typography.bodySmall.copy(fontSize = 8.sp), - modifier = Modifier - .clickable(onClick = { - val intent = Intent(Intent.ACTION_VIEW).apply { - data = Uri.parse(forecast.providerUrl) - ?: return@clickable - } - context.tryStartActivity(intent) - }) - .padding(start = 8.dp, top = 4.dp, bottom = 4.dp, end = 12.dp) - ) - } - } - } - Row( - modifier = Modifier.padding(start = 16.dp), - verticalAlignment = Alignment.CenterVertically - ) { - Text( - modifier = Modifier.weight(1f), - text = formatTemperature( - context, - forecast.temperature.toFloat(), - measurementSystem - ), - style = MaterialTheme.typography.headlineMedium, - ) - Text( - text = forecast.condition, - style = MaterialTheme.typography.labelMedium, - ) - AnimatedWeatherIcon( - modifier = Modifier.padding( - start = 8.dp, - end = 8.dp - ), - icon = weatherIconById(forecast.icon), - night = forecast.night, - WeatherIconDefaults.colors(MaterialTheme.colorScheme.surface) - ) - } + })", + style = MaterialTheme.typography.bodySmall.copy(fontSize = 8.sp), + color = MaterialTheme.colorScheme.onSurfaceVariant, + modifier = Modifier + .offset(-16.dp, 4.dp) + .clickable(onClick = { + val intent = Intent(Intent.ACTION_VIEW).apply { + data = Uri.parse(forecast.providerUrl) + ?: return@clickable + } + context.tryStartActivity(intent) + }) + .padding(horizontal = 16.dp, vertical = 4.dp) + ) + + } - Row( + Column( modifier = Modifier - .padding(start = 16.dp, end = 16.dp, bottom = 12.dp, top = 8.dp) - .fillMaxWidth(), - horizontalArrangement = Arrangement.SpaceBetween + .weight(1f) + .fillMaxHeight() + .padding( + end = 16.dp, + top = 16.dp, + bottom = 16.dp, + ), + horizontalAlignment = Alignment.End, ) { - if (forecast.humidity != null) { - Tooltip( - tooltipText = stringResource(R.string.weather_forecast_humidity) - ) { - Row( - verticalAlignment = Alignment.CenterVertically - ) { + + AnimatedWeatherIcon( + modifier = Modifier.offset(8.dp, -8.dp), + icon = weatherIconById(forecast.icon), + night = forecast.night, + WeatherIconDefaults.colors(MaterialTheme.colorScheme.surface) + ) + + CurrentWeatherDetails( + modifier = Modifier.weight(1f), + forecast = forecast, + measurementSystem = measurementSystem, + ) + } + } +} + +@Composable +private fun CurrentWeatherDetails( + modifier: Modifier = Modifier, + forecast: Forecast, + measurementSystem: MeasurementSystem +) { + val context = LocalContext.current + val resources = LocalResources.current + + val items = remember(forecast) { + buildList Unit, String>> { + if ( + forecast.precipProbability != null && forecast.precipProbability!! >= 20 || + forecast.precipitation != null && forecast.precipitation!! >= 1 + ) { + add( + @Composable { Icon( - painter = painterResource(R.drawable.humidity_percentage_20px), - modifier = Modifier.size(20.dp), + painter = painterResource(R.drawable.rainy_20px), + modifier = Modifier + .size(20.dp), + contentDescription = null, tint = MaterialTheme.colorScheme.secondary, - contentDescription = null ) - Spacer(modifier = Modifier.padding(3.dp)) - Text( - text = formatPercent(forecast.humidity!!.toFloat()), - style = MaterialTheme.typography.bodySmall, + } to if (forecast.precipProbability != null) { + formatPercent(forecast.precipProbability!!.toFloat()) + } else { + formatPrecipitation( + context, + forecast.precipitation?.toFloat() ?: 0f, + measurementSystem ) } - } - + ) } - if (forecast.windDirection != null || forecast.windSpeed != null) { - Tooltip( - tooltipText = stringResource(R.string.weather_forecast_wind) - ) { - Row( - verticalAlignment = Alignment.CenterVertically - ) { + if (forecast.uvIndex != null && forecast.uvIndex!! >= 3) { + add( + @Composable { + Icon( + painter = painterResource(R.drawable.sunny_20px), + modifier = Modifier + .size(20.dp), + contentDescription = null, + tint = MaterialTheme.colorScheme.secondary, + ) + } to "UV ${forecast.uvIndex}" + + ) + } + if (forecast.windSpeed != null && forecast.windSpeed!! >= 3) { + add( + @Composable { if (forecast.windDirection != null) { // windDirection is "fromDirection"; Wind (arrow) blows into opposite direction val angle by animateFloatAsState(forecast.windDirection!!.toFloat() + 180f) Icon( - painter = painterResource(R.drawable.north_20px), + painter = painterResource(R.drawable.navigation_20px), modifier = Modifier .rotate(angle) .size(20.dp), @@ -424,50 +454,61 @@ fun CurrentWeather( tint = MaterialTheme.colorScheme.secondary, ) } - Spacer(modifier = Modifier.padding(3.dp)) - Text( - text = if (forecast.windSpeed != null) { - formatSpeed( - context, - forecast.windSpeed!!.toFloat(), - measurementSystem - ) - } else { - windDirectionAsWord(forecast.windDirection!!) - }, - style = MaterialTheme.typography.bodySmall, - ) - } - } + } to formatSpeed( + context, + forecast.windSpeed!!.toFloat(), + measurementSystem + ) + + ) } - if (forecast.precipitation != null) { - Tooltip( - tooltipText = stringResource(id = R.string.weather_forecast_precipitation) - ) { - Row( - verticalAlignment = Alignment.CenterVertically - ) { + if (size < 4) { + add( + @Composable { Icon( - painter = painterResource(R.drawable.rainy_20px), - modifier = Modifier.size(20.dp), + painter = painterResource(R.drawable.humidity_mid_20px), + modifier = Modifier + .size(20.dp), contentDescription = null, tint = MaterialTheme.colorScheme.secondary, ) - Spacer(modifier = Modifier.padding(3.dp)) - Text( - text = if (forecast.precipProbability != null) { - formatPercent(forecast.precipProbability!!.toFloat()) - } else { - formatPrecipitation( - context, - forecast.precipitation?.toFloat() ?: 0f, - measurementSystem - ) - }, - style = MaterialTheme.typography.bodySmall, + } to formatPercent(forecast.humidity!!.toFloat()), + ) + } + if (size < 4 && forecast.clouds != null && forecast.clouds!! >= 30) { + add( + @Composable { + Icon( + painter = painterResource(R.drawable.cloud_20px), + modifier = Modifier + .size(20.dp), + contentDescription = null, + tint = MaterialTheme.colorScheme.secondary, ) - } - } + } to formatPercent(forecast.clouds!!.toFloat()), + ) + } + } + } + + FlowRow( + modifier = modifier, + verticalArrangement = Arrangement.spacedBy(8.dp), + horizontalArrangement = Arrangement.spacedBy(16.dp, Alignment.End), + ) { + for (item in items) { + Row( + modifier = Modifier, + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.SpaceBetween, + ) { + item.first() + Text( + textAlign = TextAlign.End, + modifier = Modifier.padding(start = 4.dp), + text = item.second, + style = MaterialTheme.typography.labelSmall, + ) } } } @@ -593,7 +634,7 @@ fun WeatherDaySelector( val colors = WeatherIconDefaults.colors(MaterialTheme.colorScheme.surfaceBright) val selectedColors = WeatherIconDefaults.themedColors( MaterialTheme.colorScheme.secondary, - ) + ) val listState = rememberLazyListState() LazyRow( @@ -693,9 +734,8 @@ private fun formatTime(context: Context, timestamp: Long, timeFormat: TimeFormat ) } -@Composable -private fun windDirectionAsWord(direction: Double): String { - return stringResource( +private fun windDirectionAsWord(resources: Resources, direction: Double): String { + return resources.getString( when (direction) { in 11.25..33.75 -> R.string.wind_north_north_east in 33.75..56.25 -> R.string.wind_north_east diff --git a/core/base/src/main/res/drawable/cloud_20px.xml b/core/base/src/main/res/drawable/cloud_20px.xml new file mode 100644 index 000000000..910444cb0 --- /dev/null +++ b/core/base/src/main/res/drawable/cloud_20px.xml @@ -0,0 +1,10 @@ + + + diff --git a/core/base/src/main/res/drawable/humidity_mid_20px.xml b/core/base/src/main/res/drawable/humidity_mid_20px.xml new file mode 100644 index 000000000..d6deff9d1 --- /dev/null +++ b/core/base/src/main/res/drawable/humidity_mid_20px.xml @@ -0,0 +1,10 @@ + + + diff --git a/core/base/src/main/res/drawable/navigation_20px.xml b/core/base/src/main/res/drawable/navigation_20px.xml new file mode 100644 index 000000000..fe3eb518c --- /dev/null +++ b/core/base/src/main/res/drawable/navigation_20px.xml @@ -0,0 +1,10 @@ + + + diff --git a/core/base/src/main/res/drawable/sunny_20px.xml b/core/base/src/main/res/drawable/sunny_20px.xml new file mode 100644 index 000000000..2af403a5c --- /dev/null +++ b/core/base/src/main/res/drawable/sunny_20px.xml @@ -0,0 +1,10 @@ + + + diff --git a/core/shared/src/main/java/de/mm20/launcher2/plugin/contracts/WeatherPluginContract.kt b/core/shared/src/main/java/de/mm20/launcher2/plugin/contracts/WeatherPluginContract.kt index 9846c5986..90bdc9e16 100644 --- a/core/shared/src/main/java/de/mm20/launcher2/plugin/contracts/WeatherPluginContract.kt +++ b/core/shared/src/main/java/de/mm20/launcher2/plugin/contracts/WeatherPluginContract.kt @@ -29,6 +29,7 @@ object WeatherPluginContract { val Precipitation = column("precipitation") val RainProbability = column("rain_probability") val Clouds = column("clouds") + val UvIndex = column("uv_index") val Location = column("location") val Provider = column("provider") val ProviderUrl = column("provider_url") diff --git a/data/database/src/main/java/de/mm20/launcher2/database/AppDatabase.kt b/data/database/src/main/java/de/mm20/launcher2/database/AppDatabase.kt index a75212043..2bf8e49b2 100644 --- a/data/database/src/main/java/de/mm20/launcher2/database/AppDatabase.kt +++ b/data/database/src/main/java/de/mm20/launcher2/database/AppDatabase.kt @@ -44,6 +44,7 @@ import de.mm20.launcher2.database.migrations.Migration_27_28 import de.mm20.launcher2.database.migrations.Migration_28_29 import de.mm20.launcher2.database.migrations.Migration_29_30 import de.mm20.launcher2.database.migrations.Migration_30_31 +import de.mm20.launcher2.database.migrations.Migration_31_32 import de.mm20.launcher2.database.migrations.Migration_6_7 import de.mm20.launcher2.database.migrations.Migration_7_8 import de.mm20.launcher2.database.migrations.Migration_8_9 @@ -67,7 +68,7 @@ import java.util.UUID ShapesEntity::class, TransparenciesEntity::class, TypographyEntity::class, - ], version = 31, exportSchema = true + ], version = 32, exportSchema = true ) @TypeConverters(ComponentNameConverter::class) abstract class AppDatabase : RoomDatabase() { @@ -175,6 +176,7 @@ abstract class AppDatabase : RoomDatabase() { Migration_28_29(), Migration_29_30(), Migration_30_31(), + Migration_31_32(), ).build() if (_instance == null) _instance = instance return instance diff --git a/data/database/src/main/java/de/mm20/launcher2/database/entities/ForecastEntity.kt b/data/database/src/main/java/de/mm20/launcher2/database/entities/ForecastEntity.kt index 7304ec83f..2a584f2b5 100644 --- a/data/database/src/main/java/de/mm20/launcher2/database/entities/ForecastEntity.kt +++ b/data/database/src/main/java/de/mm20/launcher2/database/entities/ForecastEntity.kt @@ -24,7 +24,9 @@ data class ForecastEntity( val provider: String, val providerUrl: String = "", @ColumnInfo(name = "rainProbability") val precipProbability: Int = -1, + @Deprecated("Deprecated") val snowProbability: Int = -1, + val uvIndex: Double = -1.0, val updateTime: Long ) { companion object { diff --git a/data/database/src/main/java/de/mm20/launcher2/database/migrations/Migration_31_32.kt b/data/database/src/main/java/de/mm20/launcher2/database/migrations/Migration_31_32.kt new file mode 100644 index 000000000..800228b44 --- /dev/null +++ b/data/database/src/main/java/de/mm20/launcher2/database/migrations/Migration_31_32.kt @@ -0,0 +1,17 @@ +package de.mm20.launcher2.database.migrations + +import androidx.room.migration.Migration +import androidx.sqlite.db.SupportSQLiteDatabase +import de.mm20.launcher2.ktx.toBytes +import de.mm20.launcher2.preferences.WidgetScreenTarget + +class Migration_31_32 : Migration(31, 32) { + + override fun migrate(db: SupportSQLiteDatabase) { + db.execSQL( + """ + ALTER TABLE `forecasts` ADD COLUMN `uvIndex` REAL NOT NULL DEFAULT -1.0; + """.trimIndent(), + ) + } +} diff --git a/data/weather/src/main/java/de/mm20/launcher2/weather/Forecast.kt b/data/weather/src/main/java/de/mm20/launcher2/weather/Forecast.kt index d5b08c79f..59d900de9 100644 --- a/data/weather/src/main/java/de/mm20/launcher2/weather/Forecast.kt +++ b/data/weather/src/main/java/de/mm20/launcher2/weather/Forecast.kt @@ -45,6 +45,8 @@ data class Forecast( val windDirection: Double? = null, /** rain, in mm per hour **/ val precipitation: Double? = null, + /** UV index **/ + val uvIndex: Double? = null, /** whether this forecast is during nighttime (whether a moon icon should be used instead of sun) **/ val night: Boolean = false, /** Location string **/ @@ -79,7 +81,7 @@ data class Forecast( windDirection = windDirection ?: -1.0, windSpeed = windSpeed ?: -1.0, snow = -1.0, - snowProbability = -1 + uvIndex = uvIndex ?: -1.0, ) } @@ -102,6 +104,7 @@ data class Forecast( updateTime = entity.updateTime, windDirection = entity.windDirection.takeIf { it >= 0.0 }, windSpeed = entity.windSpeed.takeIf { it >= 0.0 }, + uvIndex = entity.uvIndex.takeIf { it >= 0.0 }, ) companion object { diff --git a/data/weather/src/main/java/de/mm20/launcher2/weather/breezy/BreezyWeatherProvider.kt b/data/weather/src/main/java/de/mm20/launcher2/weather/breezy/BreezyWeatherProvider.kt index 76cfbc8ee..a182080b2 100644 --- a/data/weather/src/main/java/de/mm20/launcher2/weather/breezy/BreezyWeatherProvider.kt +++ b/data/weather/src/main/java/de/mm20/launcher2/weather/breezy/BreezyWeatherProvider.kt @@ -86,6 +86,7 @@ class BreezyWeatherProvider( windSpeed = hourly.windSpeed?.toDouble()?.div(3.6), precipProbability = hourly.precipProbability, windDirection = hourly.windDirection?.toDouble(), + uvIndex = hourly.uvIndex?.toDouble(), updateTime = lastUpdate, night = isNight ) diff --git a/data/weather/src/main/java/de/mm20/launcher2/weather/metno/MetNoApi.kt b/data/weather/src/main/java/de/mm20/launcher2/weather/metno/MetNoApi.kt index 4b6656c77..71c997677 100644 --- a/data/weather/src/main/java/de/mm20/launcher2/weather/metno/MetNoApi.kt +++ b/data/weather/src/main/java/de/mm20/launcher2/weather/metno/MetNoApi.kt @@ -49,12 +49,12 @@ internal data class LocationForecast( ) { @Serializable internal data class Details( - @SerialName("air_temperature") val airTemperature: Double, - @SerialName("cloud_area_fraction") val cloudAreaFraction: Double, - @SerialName("relative_humidity") val relativeHumidity: Double, - @SerialName("wind_from_direction") val windFromDirection: Double, - @SerialName("wind_speed") val windSpeed: Double, - @SerialName("air_pressure_at_sea_level") val airPressureAtSeaLevel: Double, + @SerialName("air_temperature") val airTemperature: Double?, + @SerialName("cloud_area_fraction") val cloudAreaFraction: Double?, + @SerialName("relative_humidity") val relativeHumidity: Double?, + @SerialName("wind_from_direction") val windFromDirection: Double?, + @SerialName("wind_speed") val windSpeed: Double?, + @SerialName("air_pressure_at_sea_level") val airPressureAtSeaLevel: Double?, ) } @@ -70,7 +70,8 @@ internal data class LocationForecast( @Serializable internal data class Details( - @SerialName("precipitation_amount") val precipitationAmount: Double? + @SerialName("precipitation_amount") val precipitationAmount: Double?, + @SerialName("ultraviolet_index_clear_sky_max") val ultravioletIndexClearSkyMax: Double?, ) } } diff --git a/data/weather/src/main/java/de/mm20/launcher2/weather/metno/MetNoProvider.kt b/data/weather/src/main/java/de/mm20/launcher2/weather/metno/MetNoProvider.kt index 78d8b4967..d5995eca8 100644 --- a/data/weather/src/main/java/de/mm20/launcher2/weather/metno/MetNoProvider.kt +++ b/data/weather/src/main/java/de/mm20/launcher2/weather/metno/MetNoProvider.kt @@ -97,9 +97,9 @@ internal class MetNoProvider( forecasts.add( Forecast( timestamp = timestamp, - temperature = details.airTemperature + 273.15, + temperature = (details.airTemperature ?: continue) + 273.15, updateTime = updatedAt, - clouds = details.cloudAreaFraction.roundToInt(), + clouds = details.cloudAreaFraction?.roundToInt(), humidity = details.relativeHumidity, windDirection = details.windFromDirection, windSpeed = details.windSpeed, @@ -110,6 +110,7 @@ internal class MetNoProvider( icon = iconForCode(symbolCode), condition = conditionForCode(symbolCode), precipitation = precipitationAmount, + uvIndex = nextHours.details?.ultravioletIndexClearSkyMax, night = isNight(timestamp, lat, lon) ) @@ -239,7 +240,7 @@ internal class MetNoProvider( "lightrain", "lightrainshowers" -> Forecast.LIGHT_RAIN "heavyrain", "heavyrainshowers" -> Forecast.HEAVY_RAIN - "rain" -> Forecast.RAIN + "rain", "rainshowers" -> Forecast.RAIN "lightsleet", "lightsleetshowers", "heavysleet", "heavysleetshowers", "sleet" -> Forecast.SLEET diff --git a/data/weather/src/main/java/de/mm20/launcher2/weather/openweathermap/OpenWeatherMapApi.kt b/data/weather/src/main/java/de/mm20/launcher2/weather/openweathermap/OpenWeatherMapApi.kt index 729224b7d..b30fa11fb 100644 --- a/data/weather/src/main/java/de/mm20/launcher2/weather/openweathermap/OpenWeatherMapApi.kt +++ b/data/weather/src/main/java/de/mm20/launcher2/weather/openweathermap/OpenWeatherMapApi.kt @@ -68,14 +68,14 @@ data class WeatherResultClouds( @Serializable data class CurrentWeatherResultRain( - @SerialName("1h") val oneHour: Double, - @SerialName("3h") val threeHours: Double, + @SerialName("1h") val oneHour: Double?, + @SerialName("3h") val threeHours: Double?, ) @Serializable data class CurrentWeatherResultSnow( - @SerialName("1h") val oneHour: Double, - @SerialName("3h") val threeHours: Double, + @SerialName("1h") val oneHour: Double?, + @SerialName("3h") val threeHours: Double?, ) @Serializable @@ -117,7 +117,7 @@ data class ForecastResultSnow( @Serializable data class ForecastResultSys( - val pod: String, + val pod: String?, ) @Serializable diff --git a/data/weather/src/main/java/de/mm20/launcher2/weather/plugin/PluginWeatherProvider.kt b/data/weather/src/main/java/de/mm20/launcher2/weather/plugin/PluginWeatherProvider.kt index 50bac688f..5afbe8e9c 100644 --- a/data/weather/src/main/java/de/mm20/launcher2/weather/plugin/PluginWeatherProvider.kt +++ b/data/weather/src/main/java/de/mm20/launcher2/weather/plugin/PluginWeatherProvider.kt @@ -118,6 +118,7 @@ internal class PluginWeatherProvider( provider = cursor[ForecastColumns.Provider] ?: continue, providerUrl = cursor[ForecastColumns.ProviderUrl] ?: "", clouds = cursor[ForecastColumns.Clouds], + uvIndex = cursor[ForecastColumns.UvIndex], humidity = cursor[ForecastColumns.Humidity]?.toDouble(), precipitation = cursor[ForecastColumns.Precipitation], precipProbability = cursor[ForecastColumns.RainProbability], diff --git a/plugins/sdk/src/main/java/de/mm20/launcher2/sdk/weather/Forecast.kt b/plugins/sdk/src/main/java/de/mm20/launcher2/sdk/weather/Forecast.kt index 191e55a0c..726333b56 100644 --- a/plugins/sdk/src/main/java/de/mm20/launcher2/sdk/weather/Forecast.kt +++ b/plugins/sdk/src/main/java/de/mm20/launcher2/sdk/weather/Forecast.kt @@ -180,6 +180,11 @@ data class Forecast( * Clouds in percent */ val clouds: Int? = null, + + /** + * UV index (0-11) + */ + val uvIndex: Double? = null, /** * Location name */ diff --git a/plugins/sdk/src/main/java/de/mm20/launcher2/sdk/weather/WeatherProvider.kt b/plugins/sdk/src/main/java/de/mm20/launcher2/sdk/weather/WeatherProvider.kt index faaecbeeb..972281c8a 100644 --- a/plugins/sdk/src/main/java/de/mm20/launcher2/sdk/weather/WeatherProvider.kt +++ b/plugins/sdk/src/main/java/de/mm20/launcher2/sdk/weather/WeatherProvider.kt @@ -86,6 +86,7 @@ abstract class WeatherProvider( put(ForecastColumns.Precipitation, it.precipitation?.mm) put(ForecastColumns.RainProbability, it.rainProbability) put(ForecastColumns.Clouds, it.clouds) + put(ForecastColumns.UvIndex, it.uvIndex) put(ForecastColumns.Location, it.location) put(ForecastColumns.Provider, it.provider) put(ForecastColumns.ProviderUrl, it.providerUrl)