From de8e83aa380c757e0abc5894b401ab4a3b09719d Mon Sep 17 00:00:00 2001 From: MM20 <15646950+MM2-0@users.noreply.github.com> Date: Fri, 13 Feb 2026 11:16:17 +0100 Subject: [PATCH] Rename weather icons, add monochrome variant --- .../component/weather/AnimatedWeatherIcon.kt | 335 +++++++++--------- .../ui/component/weather/WeatherIcon.kt | 238 ++++--------- .../ui/component/weather/WeatherIconColors.kt | 142 ++++++-- .../launcher/widgets/weather/WeatherWidget.kt | 152 +++++--- .../de/mm20/launcher2/weather/WeatherIcon.kt | 41 ++- .../mm20/launcher2/weather/DailyForecast.kt | 193 +++++----- .../de/mm20/launcher2/weather/Forecast.kt | 208 +++++------ .../java/de/mm20/launcher2/weather/Weather.kt | 47 --- .../weather/breezy/BreezyWeatherProvider.kt | 6 +- .../weather/brightsky/BrightSkyProvider.kt | 4 +- .../launcher2/weather/metno/MetNoProvider.kt | 51 +-- .../openweathermap/OpenWeatherMapProvider.kt | 29 +- .../weather/plugin/PluginWeatherProvider.kt | 40 ++- 13 files changed, 759 insertions(+), 727 deletions(-) delete mode 100644 data/weather/src/main/java/de/mm20/launcher2/weather/Weather.kt diff --git a/app/ui/src/main/java/de/mm20/launcher2/ui/component/weather/AnimatedWeatherIcon.kt b/app/ui/src/main/java/de/mm20/launcher2/ui/component/weather/AnimatedWeatherIcon.kt index 039500294..20f01cbbc 100644 --- a/app/ui/src/main/java/de/mm20/launcher2/ui/component/weather/AnimatedWeatherIcon.kt +++ b/app/ui/src/main/java/de/mm20/launcher2/ui/component/weather/AnimatedWeatherIcon.kt @@ -3,13 +3,35 @@ package de.mm20.launcher2.ui.component.weather import androidx.compose.animation.Crossfade import androidx.compose.animation.animateColor import androidx.compose.animation.animateColorAsState -import androidx.compose.animation.core.* +import androidx.compose.animation.core.InfiniteRepeatableSpec +import androidx.compose.animation.core.LinearEasing +import androidx.compose.animation.core.animateFloat +import androidx.compose.animation.core.animateFloatAsState +import androidx.compose.animation.core.animateOffset +import androidx.compose.animation.core.rememberInfiniteTransition +import androidx.compose.animation.core.tween +import androidx.compose.animation.core.updateTransition import androidx.compose.animation.graphics.res.animatedVectorResource import androidx.compose.animation.graphics.res.rememberAnimatedVectorPainter import androidx.compose.animation.graphics.vector.AnimatedImageVector -import androidx.compose.foundation.layout.* -import androidx.compose.material3.* -import androidx.compose.runtime.* +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.offset +import androidx.compose.foundation.layout.size +import androidx.compose.material3.Button +import androidx.compose.material3.DropdownMenu +import androidx.compose.material3.DropdownMenuItem +import androidx.compose.material3.Icon +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clipToBounds @@ -41,9 +63,7 @@ fun AnimatedWeatherIcon( SunMoon(icon, night, colors) Precipitation(icon, colors) LightningBolt(icon, colors) - LightningBolt2(icon, colors) Cloud1(icon, colors) - Wind2(icon, colors) Cloud2(icon, colors) Cloud3(icon, colors) Fog(icon, colors) @@ -63,18 +83,16 @@ private fun SunMoon(icon: WeatherIcon, night: Boolean, colors: WeatherIconColors when (it) { WeatherIcon.Clear, WeatherIcon.PartlyCloudy, - WeatherIcon.BrokenClouds, WeatherIcon.Haze -> 1f - WeatherIcon.MostlyCloudy -> 0.8f + else -> 0f } } val offset by transition.animateOffset(label = "sunOffset") { when (it) { WeatherIcon.Clear, - WeatherIcon.BrokenClouds, WeatherIcon.Haze -> Offset.Zero - WeatherIcon.MostlyCloudy -> Offset(6f, -4f) + WeatherIcon.PartlyCloudy -> Offset(3f, -2f) else -> Offset.Zero } @@ -97,19 +115,17 @@ private fun LightningBolt(icon: WeatherIcon, colors: WeatherIconColors) { val scale by transition.animateFloat(label = "scale") { when (it) { - WeatherIcon.Thunderstorm, - WeatherIcon.ThunderstormWithRain, - WeatherIcon.HeavyThunderstormWithRain, - WeatherIcon.HeavyThunderstorm -> 0.6f + WeatherIcon.Thunder, + WeatherIcon.Thunderstorm -> 0.6f + else -> 0f } } val offset by transition.animateOffset(label = "offset") { when (it) { - WeatherIcon.ThunderstormWithRain, + WeatherIcon.Thunder, WeatherIcon.Thunderstorm -> Offset(1f, 8f) - WeatherIcon.HeavyThunderstormWithRain, - WeatherIcon.HeavyThunderstorm -> Offset(6f, 8f) + else -> Offset.Zero } } @@ -125,35 +141,6 @@ private fun LightningBolt(icon: WeatherIcon, colors: WeatherIconColors) { ) } -@Composable -private fun LightningBolt2(icon: WeatherIcon, colors: WeatherIconColors) { - val transition = updateTransition(targetState = icon, "AnimatedWeatherIconBolt2") - - val scale by transition.animateFloat(label = "scale") { - when (it) { - WeatherIcon.HeavyThunderstormWithRain, - WeatherIcon.HeavyThunderstorm -> 0.5f - else -> 0f - } - } - val offset by transition.animateOffset(label = "offset") { - when (it) { - WeatherIcon.HeavyThunderstormWithRain, - WeatherIcon.HeavyThunderstorm -> Offset(-9f, 5f) - else -> Offset.Zero - } - } - - Icon( - painter = painterResource(R.drawable.bolt_24px_filled), - null, - modifier = Modifier - .size(32.dp) - .offset(offset.x.dp, offset.y.dp) - .scale(scale), - tint = colors.lightningBolt - ) -} @Composable private fun Cloud1(icon: WeatherIcon, colors: WeatherIconColors) { @@ -162,64 +149,60 @@ private fun Cloud1(icon: WeatherIcon, colors: WeatherIconColors) { val scale by transition.animateFloat(label = "scale") { when (it) { WeatherIcon.Clear -> 0f + WeatherIcon.Thunder, WeatherIcon.Thunderstorm, - WeatherIcon.ThunderstormWithRain, - WeatherIcon.HeavyThunderstorm, - WeatherIcon.HeavyThunderstormWithRain, - WeatherIcon.Showers, - WeatherIcon.Drizzle, + WeatherIcon.LightRain, + WeatherIcon.Rain, + WeatherIcon.HeavyRain, WeatherIcon.Sleet, WeatherIcon.Snow, WeatherIcon.Hail, WeatherIcon.Wind, - WeatherIcon.Cloudy, - WeatherIcon.Storm, + WeatherIcon.Overcast, + WeatherIcon.Wind, WeatherIcon.Fog -> 1.4f - WeatherIcon.MostlyCloudy, + WeatherIcon.PartlyCloudy -> 1f - WeatherIcon.BrokenClouds -> 0.9f else -> 0f } } val offset by transition.animateOffset(label = "offset") { when (it) { - WeatherIcon.Showers, - WeatherIcon.Drizzle, + WeatherIcon.LightRain, + WeatherIcon.Rain, + WeatherIcon.HeavyRain, WeatherIcon.Sleet, WeatherIcon.Snow, WeatherIcon.Hail, - WeatherIcon.Thunderstorm, - WeatherIcon.ThunderstormWithRain, - WeatherIcon.HeavyThunderstorm, - WeatherIcon.HeavyThunderstormWithRain -> Offset(0f, -14f) - WeatherIcon.Cloudy, + WeatherIcon.Thunder, + WeatherIcon.Thunderstorm -> Offset(0f, -14f) + + WeatherIcon.Overcast, WeatherIcon.Wind, - WeatherIcon.Storm, WeatherIcon.Fog -> Offset(0f, -5f) - WeatherIcon.MostlyCloudy -> Offset(-5f, 0f) + WeatherIcon.PartlyCloudy -> Offset(-3f, 4f) - WeatherIcon.BrokenClouds -> Offset(-5f, 7f) else -> Offset.Zero } } val color by transition.animateColor(label = "color") { when (it) { WeatherIcon.Thunderstorm, - WeatherIcon.ThunderstormWithRain, - WeatherIcon.HeavyThunderstorm, - WeatherIcon.HeavyThunderstormWithRain, - WeatherIcon.Storm -> colors.cloudDark2 - WeatherIcon.Showers, + WeatherIcon.Thunder, + WeatherIcon.Wind -> colors.cloudDark2 + + WeatherIcon.Rain, + WeatherIcon.HeavyRain, WeatherIcon.Sleet, WeatherIcon.Hail, - WeatherIcon.Cloudy, + WeatherIcon.Overcast, WeatherIcon.Wind, WeatherIcon.Fog -> colors.cloudDark1 - WeatherIcon.Drizzle, + + WeatherIcon.LightRain, WeatherIcon.Snow -> colors.cloudMedium2 - WeatherIcon.MostlyCloudy -> colors.cloudLight2 - WeatherIcon.PartlyCloudy, - WeatherIcon.BrokenClouds -> colors.cloudLight1 + + WeatherIcon.PartlyCloudy -> colors.cloudLight1 else -> colors.cloudMedium2 } } @@ -241,61 +224,55 @@ private fun Cloud2(icon: WeatherIcon, colors: WeatherIconColors) { val scale by transition.animateFloat(label = "scale") { when (it) { + WeatherIcon.Thunder, WeatherIcon.Thunderstorm, - WeatherIcon.ThunderstormWithRain, - WeatherIcon.HeavyThunderstorm, - WeatherIcon.HeavyThunderstormWithRain, - WeatherIcon.MostlyCloudy, - WeatherIcon.Showers, - WeatherIcon.Drizzle, + WeatherIcon.Rain, + WeatherIcon.HeavyRain, + WeatherIcon.LightRain, WeatherIcon.Sleet, WeatherIcon.Snow, WeatherIcon.Hail, - WeatherIcon.Cloudy, + WeatherIcon.Overcast, WeatherIcon.Wind, - WeatherIcon.Storm, WeatherIcon.Fog -> 1.1f - WeatherIcon.BrokenClouds -> 0.7f + else -> 0f } } val offset by transition.animateOffset(label = "offset") { when (it) { - WeatherIcon.Showers, - WeatherIcon.Drizzle, + WeatherIcon.Rain, + WeatherIcon.LightRain, + WeatherIcon.HeavyRain, WeatherIcon.Sleet, WeatherIcon.Snow, WeatherIcon.Hail, WeatherIcon.Thunderstorm, - WeatherIcon.ThunderstormWithRain, - WeatherIcon.HeavyThunderstorm, - WeatherIcon.HeavyThunderstormWithRain -> Offset(-6f, -6f) - WeatherIcon.Cloudy, + WeatherIcon.Thunder -> Offset(-6f, -6f) + + WeatherIcon.Overcast, WeatherIcon.Wind, - WeatherIcon.Storm, WeatherIcon.Fog -> Offset(-6f, 3f) - WeatherIcon.BrokenClouds -> Offset(9f, -9f) - WeatherIcon.MostlyCloudy -> Offset(4f, 5f) + else -> Offset.Zero } } val color by transition.animateColor(label = "color") { when (it) { - WeatherIcon.BrokenClouds -> colors.cloudLight2 WeatherIcon.Thunderstorm, - WeatherIcon.ThunderstormWithRain, - WeatherIcon.HeavyThunderstorm, - WeatherIcon.HeavyThunderstormWithRain, - WeatherIcon.Storm -> colors.cloudMedium2 - WeatherIcon.Showers, - WeatherIcon.Drizzle, + WeatherIcon.Thunder, + WeatherIcon.Wind -> colors.cloudMedium2 + + WeatherIcon.Rain, + WeatherIcon.HeavyRain, + WeatherIcon.LightRain, WeatherIcon.Sleet, WeatherIcon.Snow, WeatherIcon.Hail, - WeatherIcon.Cloudy, + WeatherIcon.Overcast, WeatherIcon.Wind, WeatherIcon.Fog -> colors.cloudMedium1 - WeatherIcon.MostlyCloudy -> colors.cloudMedium1 + else -> colors.cloudMedium2 } } @@ -317,52 +294,53 @@ private fun Cloud3(icon: WeatherIcon, colors: WeatherIconColors) { val scale by transition.animateFloat(label = "scale") { when (it) { - WeatherIcon.Showers, - WeatherIcon.Drizzle, + WeatherIcon.Rain, + WeatherIcon.LightRain, + WeatherIcon.HeavyRain, WeatherIcon.Sleet, WeatherIcon.Snow, WeatherIcon.Hail, WeatherIcon.Thunderstorm, - WeatherIcon.ThunderstormWithRain, - WeatherIcon.HeavyThunderstorm, - WeatherIcon.HeavyThunderstormWithRain, - WeatherIcon.Cloudy, - WeatherIcon.Wind, - WeatherIcon.Storm -> 1f + WeatherIcon.Thunder, + WeatherIcon.Overcast, + WeatherIcon.Wind -> 1f + else -> 0f } } val offset by transition.animateOffset(label = "offset") { when (it) { - WeatherIcon.Showers, - WeatherIcon.Drizzle, + WeatherIcon.LightRain, + WeatherIcon.HeavyRain, + WeatherIcon.Rain, WeatherIcon.Sleet, WeatherIcon.Snow, WeatherIcon.Hail, WeatherIcon.Thunderstorm, - WeatherIcon.ThunderstormWithRain, - WeatherIcon.HeavyThunderstorm, - WeatherIcon.HeavyThunderstormWithRain -> Offset(6f, -4f) - WeatherIcon.Cloudy, - WeatherIcon.Wind, - WeatherIcon.Storm -> Offset(6f, 5f) + WeatherIcon.Thunder -> Offset(6f, -4f) + + WeatherIcon.Overcast, + WeatherIcon.Wind -> Offset(6f, 5f) + else -> Offset.Zero } } val color by transition.animateColor(label = "color") { when (it) { WeatherIcon.Thunderstorm, - WeatherIcon.ThunderstormWithRain, - WeatherIcon.HeavyThunderstorm, - WeatherIcon.HeavyThunderstormWithRain, - WeatherIcon.Storm -> colors.cloudDark1 - WeatherIcon.Showers, + WeatherIcon.Thunder, + WeatherIcon.Wind -> colors.cloudDark1 + + WeatherIcon.Rain, + WeatherIcon.HeavyRain, WeatherIcon.Sleet, WeatherIcon.Hail, - WeatherIcon.Cloudy, + WeatherIcon.Overcast, WeatherIcon.Wind -> colors.cloudMedium2 - WeatherIcon.Drizzle, + + WeatherIcon.LightRain, WeatherIcon.Snow -> colors.cloudLight2 + else -> colors.cloudMedium2 } } @@ -381,7 +359,7 @@ private fun Cloud3(icon: WeatherIcon, colors: WeatherIconColors) { @Composable private fun Hot(icon: WeatherIcon, colors: WeatherIconColors) { val scale by animateFloatAsState( - if (icon == WeatherIcon.Hot) { + if (icon == WeatherIcon.ExtremeHeat) { 1f } else { 0f @@ -400,7 +378,7 @@ private fun Hot(icon: WeatherIcon, colors: WeatherIconColors) { @Composable private fun Cold(icon: WeatherIcon, colors: WeatherIconColors) { val scale by animateFloatAsState( - if (icon == WeatherIcon.Cold) { + if (icon == WeatherIcon.ExtremeCold) { 1f } else { 0f @@ -419,7 +397,7 @@ private fun Cold(icon: WeatherIcon, colors: WeatherIconColors) { @Composable private fun Wind1(icon: WeatherIcon, colors: WeatherIconColors) { val scale by animateFloatAsState( - if (icon == WeatherIcon.Wind || icon == WeatherIcon.Storm) { + if (icon == WeatherIcon.Wind) { 0.6f } else { 0f @@ -436,38 +414,38 @@ private fun Wind1(icon: WeatherIcon, colors: WeatherIconColors) { ) } -@Composable -private fun Wind2(icon: WeatherIcon, colors: WeatherIconColors) { - val scale by animateFloatAsState( - if (icon == WeatherIcon.Storm) { - 0.6f - } else { - 0f - } - ) - Icon( - painterResource(R.drawable.air_20px), - null, - modifier = Modifier - .scale(scale) - .offset(8.dp, -1.dp) - .size(32.dp), - tint = colors.windDark - ) -} @Preview @Composable fun AnimatedWeatherIconTestPanel() { - var icon by remember { mutableStateOf(WeatherIcon.ThunderstormWithRain) } + var icon by remember { mutableStateOf(WeatherIcon.Thunderstorm) } var night by remember { mutableStateOf(false) } Row { - val icons = WeatherIcon.values() + val icons = WeatherIcon.entries Column( - modifier = Modifier.weight(1f) + modifier = Modifier.weight(1f), + verticalArrangement = Arrangement.spacedBy(8.dp) ) { AnimatedWeatherIcon(icon = icon, night = night) WeatherIcon(icon = icon, night = night) + + val monochromeColors = WeatherIconDefaults.monochromeColors( + MaterialTheme.colorScheme.secondary + ) + + AnimatedWeatherIcon( + modifier = Modifier.background(MaterialTheme.colorScheme.secondary), + icon = icon, + night = night, + colors = monochromeColors + ) + + WeatherIcon( + modifier = Modifier.background(MaterialTheme.colorScheme.secondary), + icon = icon, + night = night, + colors = monochromeColors + ) } Column { @@ -510,7 +488,7 @@ private fun Precipitation(icon: WeatherIcon, colors: WeatherIconColors) { ) { Crossfade(icon) { when (it) { - WeatherIcon.Drizzle -> { + WeatherIcon.LightRain, WeatherIcon.Rain -> { val anim = rememberInfiniteTransition() val animProgress by anim.animateFloat( initialValue = 0f, targetValue = 1f, animationSpec = InfiniteRepeatableSpec( @@ -526,6 +504,7 @@ private fun Precipitation(icon: WeatherIcon, colors: WeatherIconColors) { tint = colors.rain ) } + WeatherIcon.Hail -> { val anim = rememberInfiniteTransition() val animProgress by anim.animateFloat( @@ -542,6 +521,7 @@ private fun Precipitation(icon: WeatherIcon, colors: WeatherIconColors) { tint = colors.hail, ) } + WeatherIcon.Snow -> { val anim = rememberInfiniteTransition() val animProgress by anim.animateFloat( @@ -561,9 +541,9 @@ private fun Precipitation(icon: WeatherIcon, colors: WeatherIconColors) { tint = colors.snow, ) } - WeatherIcon.Showers, - WeatherIcon.ThunderstormWithRain, - WeatherIcon.HeavyThunderstormWithRain -> { + + WeatherIcon.HeavyRain, + WeatherIcon.Thunderstorm -> { val anim = rememberInfiniteTransition() val animProgress by anim.animateFloat( initialValue = 0f, targetValue = 1f, animationSpec = InfiniteRepeatableSpec( @@ -579,6 +559,7 @@ private fun Precipitation(icon: WeatherIcon, colors: WeatherIconColors) { tint = colors.rain, ) } + WeatherIcon.Sleet -> { val anim = rememberInfiniteTransition() val animProgress by anim.animateFloat( @@ -603,6 +584,7 @@ private fun Precipitation(icon: WeatherIcon, colors: WeatherIconColors) { tint = colors.snow, ) } + else -> {} } } @@ -625,25 +607,36 @@ private fun Fog(icon: WeatherIcon, colors: WeatherIconColors) { } enum class WeatherIcon { - None, + Unknown, + Clear, - Cloudy, - Cold, - Drizzle, - Haze, - Fog, - Hail, - HeavyThunderstorm, - HeavyThunderstormWithRain, - Hot, - MostlyCloudy, PartlyCloudy, - Showers, - Sleet, - Snow, - Storm, + Overcast, + + LightRain, + Rain, + HeavyRain, + + /** + * Thunder and lightning without rain + */ + Thunder, + + /** + * Thunder and lightning with rain + */ Thunderstorm, - ThunderstormWithRain, + + Snow, + Sleet, + Hail, + + ExtremeHeat, + ExtremeCold, + Wind, - BrokenClouds + + Fog, + Haze, + } \ No newline at end of file diff --git a/app/ui/src/main/java/de/mm20/launcher2/ui/component/weather/WeatherIcon.kt b/app/ui/src/main/java/de/mm20/launcher2/ui/component/weather/WeatherIcon.kt index d051c277f..e2233ee21 100644 --- a/app/ui/src/main/java/de/mm20/launcher2/ui/component/weather/WeatherIcon.kt +++ b/app/ui/src/main/java/de/mm20/launcher2/ui/component/weather/WeatherIcon.kt @@ -43,10 +43,7 @@ private fun SunMoon(icon: WeatherIcon, night: Boolean, colors: WeatherIconColors when (icon) { WeatherIcon.Clear, WeatherIcon.PartlyCloudy, - WeatherIcon.BrokenClouds, - WeatherIcon.Haze, - WeatherIcon.MostlyCloudy -> { - } + WeatherIcon.Haze, -> {} else -> return } @@ -56,15 +53,10 @@ private fun SunMoon(icon: WeatherIcon, night: Boolean, colors: WeatherIconColors val scale = when (icon) { WeatherIcon.Clear, WeatherIcon.PartlyCloudy, - WeatherIcon.BrokenClouds, WeatherIcon.Haze -> 1f - - WeatherIcon.MostlyCloudy -> 0.8f - else -> 0f } val offset = when (icon) { - WeatherIcon.MostlyCloudy -> DpOffset(2.dp, -3.dp) WeatherIcon.PartlyCloudy -> DpOffset(2.dp, -2.dp) else -> DpOffset.Zero } @@ -85,19 +77,16 @@ private fun Cloud1(icon: WeatherIcon, colors: WeatherIconColors) { when (icon) { WeatherIcon.Thunderstorm, - WeatherIcon.ThunderstormWithRain, - WeatherIcon.HeavyThunderstorm, - WeatherIcon.HeavyThunderstormWithRain, - WeatherIcon.Showers, - WeatherIcon.Drizzle, + WeatherIcon.Thunder, + WeatherIcon.Rain, + WeatherIcon.LightRain, + WeatherIcon.HeavyRain, WeatherIcon.Sleet, WeatherIcon.Snow, WeatherIcon.Hail, - WeatherIcon.Cloudy, + WeatherIcon.Overcast, WeatherIcon.Fog, - WeatherIcon.MostlyCloudy, - WeatherIcon.PartlyCloudy, - WeatherIcon.BrokenClouds -> { + WeatherIcon.PartlyCloudy -> { } else -> return @@ -105,62 +94,48 @@ private fun Cloud1(icon: WeatherIcon, colors: WeatherIconColors) { val scale = when (icon) { WeatherIcon.Thunderstorm, - WeatherIcon.ThunderstormWithRain, - WeatherIcon.HeavyThunderstorm, - WeatherIcon.HeavyThunderstormWithRain, - WeatherIcon.Showers, - WeatherIcon.Drizzle, + WeatherIcon.Thunder, + WeatherIcon.Rain, + WeatherIcon.LightRain, + WeatherIcon.HeavyRain, WeatherIcon.Sleet, WeatherIcon.Snow, WeatherIcon.Hail, - WeatherIcon.Cloudy, + WeatherIcon.Overcast, WeatherIcon.Fog -> 1.4f - WeatherIcon.MostlyCloudy, WeatherIcon.PartlyCloudy -> 1f - - WeatherIcon.BrokenClouds -> 0.9f - else -> 0f } val offset = when (icon) { - WeatherIcon.Showers, - WeatherIcon.Drizzle, + WeatherIcon.HeavyRain, + WeatherIcon.Rain, + WeatherIcon.LightRain, WeatherIcon.Sleet, WeatherIcon.Snow, WeatherIcon.Hail, WeatherIcon.Thunderstorm, - WeatherIcon.ThunderstormWithRain, - WeatherIcon.HeavyThunderstorm, - WeatherIcon.HeavyThunderstormWithRain -> DpOffset(0.dp, -7.dp) + WeatherIcon.Thunder -> DpOffset(0.dp, -7.dp) - WeatherIcon.Cloudy, + WeatherIcon.Overcast, WeatherIcon.Fog -> DpOffset(0.dp, -2.5.dp) - WeatherIcon.MostlyCloudy -> DpOffset(-2.5.dp, 0.dp) WeatherIcon.PartlyCloudy -> DpOffset(-1.5.dp, 2.dp) - WeatherIcon.BrokenClouds -> DpOffset(-2.5.dp, 3.5.dp) - else -> DpOffset.Zero } val color = when (icon) { WeatherIcon.Thunderstorm, - WeatherIcon.ThunderstormWithRain, - WeatherIcon.HeavyThunderstorm, - WeatherIcon.HeavyThunderstormWithRain -> colors.cloudDark2 + WeatherIcon.Thunder -> colors.cloudDark2 - WeatherIcon.Showers, + WeatherIcon.Rain, + WeatherIcon.HeavyRain, WeatherIcon.Sleet, WeatherIcon.Hail, - WeatherIcon.Cloudy, + WeatherIcon.Overcast, WeatherIcon.Fog -> colors.cloudDark1 - WeatherIcon.Drizzle, + WeatherIcon.LightRain, WeatherIcon.Snow -> colors.cloudMedium2 - WeatherIcon.MostlyCloudy -> colors.cloudLight2 - WeatherIcon.PartlyCloudy, - WeatherIcon.BrokenClouds -> colors.cloudLight1 - - else -> colors.cloudMedium2 + WeatherIcon.PartlyCloudy -> colors.cloudLight1 } Icon( @@ -179,18 +154,15 @@ private fun Cloud2(icon: WeatherIcon, colors: WeatherIconColors) { when (icon) { WeatherIcon.Thunderstorm, - WeatherIcon.ThunderstormWithRain, - WeatherIcon.HeavyThunderstorm, - WeatherIcon.HeavyThunderstormWithRain, - WeatherIcon.MostlyCloudy, - WeatherIcon.Showers, - WeatherIcon.Drizzle, + WeatherIcon.Thunder, + WeatherIcon.Rain, + WeatherIcon.HeavyRain, + WeatherIcon.LightRain, WeatherIcon.Sleet, WeatherIcon.Snow, WeatherIcon.Hail, - WeatherIcon.Cloudy, - WeatherIcon.Fog, - WeatherIcon.BrokenClouds -> { + WeatherIcon.Overcast, + WeatherIcon.Fog -> { } else -> return @@ -198,56 +170,41 @@ private fun Cloud2(icon: WeatherIcon, colors: WeatherIconColors) { val scale = when (icon) { WeatherIcon.Thunderstorm, - WeatherIcon.ThunderstormWithRain, - WeatherIcon.HeavyThunderstorm, - WeatherIcon.HeavyThunderstormWithRain, - WeatherIcon.MostlyCloudy, - WeatherIcon.Showers, - WeatherIcon.Drizzle, + WeatherIcon.Thunder, + WeatherIcon.Rain, + WeatherIcon.LightRain, + WeatherIcon.HeavyRain, WeatherIcon.Sleet, WeatherIcon.Snow, WeatherIcon.Hail, - WeatherIcon.Cloudy, + WeatherIcon.Overcast, WeatherIcon.Fog -> 1.1f - - WeatherIcon.BrokenClouds -> 0.7f - else -> 0f } val offset = when (icon) { - WeatherIcon.Showers, - WeatherIcon.Drizzle, + WeatherIcon.Rain, + WeatherIcon.HeavyRain, + WeatherIcon.LightRain, WeatherIcon.Sleet, WeatherIcon.Snow, WeatherIcon.Hail, WeatherIcon.Thunderstorm, - WeatherIcon.ThunderstormWithRain, - WeatherIcon.HeavyThunderstorm, - WeatherIcon.HeavyThunderstormWithRain -> DpOffset(-3.dp, -3.dp) + WeatherIcon.Thunder -> DpOffset(-3.dp, -3.dp) - WeatherIcon.Cloudy, + WeatherIcon.Overcast, WeatherIcon.Fog -> DpOffset(-3.dp, 1.5.dp) - - WeatherIcon.BrokenClouds -> DpOffset(4.5.dp, -4.5.dp) - WeatherIcon.MostlyCloudy -> DpOffset(2.dp, 2.5.dp) - else -> DpOffset.Zero } val color = when (icon) { - WeatherIcon.BrokenClouds -> colors.cloudLight2 WeatherIcon.Thunderstorm, - WeatherIcon.ThunderstormWithRain, - WeatherIcon.HeavyThunderstorm, - WeatherIcon.HeavyThunderstormWithRain -> colors.cloudMedium2 + WeatherIcon.Thunder -> colors.cloudMedium2 - WeatherIcon.Showers, - WeatherIcon.Drizzle, + WeatherIcon.Rain, + WeatherIcon.HeavyRain, + WeatherIcon.LightRain, WeatherIcon.Sleet, WeatherIcon.Snow, WeatherIcon.Hail, - WeatherIcon.Cloudy, + WeatherIcon.Overcast, WeatherIcon.Fog -> colors.cloudMedium1 - - WeatherIcon.MostlyCloudy -> colors.cloudMedium1 - else -> colors.cloudMedium2 } Icon( @@ -264,52 +221,41 @@ private fun Cloud2(icon: WeatherIcon, colors: WeatherIconColors) { @Composable private fun Cloud3(icon: WeatherIcon, colors: WeatherIconColors) { when (icon) { - WeatherIcon.Showers, - WeatherIcon.Drizzle, + WeatherIcon.LightRain, + WeatherIcon.HeavyRain, + WeatherIcon.Rain, WeatherIcon.Sleet, WeatherIcon.Snow, WeatherIcon.Hail, WeatherIcon.Thunderstorm, - WeatherIcon.ThunderstormWithRain, - WeatherIcon.HeavyThunderstorm, - WeatherIcon.HeavyThunderstormWithRain, - WeatherIcon.Cloudy -> { - - } + WeatherIcon.Thunder, + WeatherIcon.Overcast -> {} else -> return } val offset = when (icon) { - WeatherIcon.Showers, - WeatherIcon.Drizzle, + WeatherIcon.LightRain, + WeatherIcon.HeavyRain, + WeatherIcon.Rain, WeatherIcon.Sleet, WeatherIcon.Snow, WeatherIcon.Hail, WeatherIcon.Thunderstorm, - WeatherIcon.ThunderstormWithRain, - WeatherIcon.HeavyThunderstorm, - WeatherIcon.HeavyThunderstormWithRain -> DpOffset(3.dp, -2.dp) - - WeatherIcon.Cloudy, - WeatherIcon.Fog -> DpOffset(3.dp, 2.5.dp) - - else -> DpOffset.Zero + WeatherIcon.Thunder -> DpOffset(3.dp, -2.dp) + WeatherIcon.Overcast -> DpOffset(3.dp, 2.5.dp) } val color = when (icon) { WeatherIcon.Thunderstorm, - WeatherIcon.ThunderstormWithRain, - WeatherIcon.HeavyThunderstorm, - WeatherIcon.HeavyThunderstormWithRain -> colors.cloudDark1 + WeatherIcon.Thunder -> colors.cloudDark1 - WeatherIcon.Showers, + WeatherIcon.Rain, + WeatherIcon.HeavyRain, WeatherIcon.Sleet, WeatherIcon.Hail, - WeatherIcon.Cloudy -> colors.cloudMedium2 + WeatherIcon.Overcast -> colors.cloudMedium2 - WeatherIcon.Drizzle, + WeatherIcon.LightRain, WeatherIcon.Snow -> colors.cloudLight2 - - else -> colors.cloudMedium2 } Icon( @@ -345,30 +291,26 @@ private fun Precipitation(icon: WeatherIcon, colors: WeatherIconColors) { } when (icon) { - WeatherIcon.Showers, - WeatherIcon.Drizzle, + WeatherIcon.Rain, + WeatherIcon.LightRain, + WeatherIcon.HeavyRain, WeatherIcon.Hail, WeatherIcon.Snow, - WeatherIcon.ThunderstormWithRain, - WeatherIcon.HeavyThunderstormWithRain -> { + WeatherIcon.Thunderstorm -> { } - else -> return } val vector = when (icon) { - WeatherIcon.Drizzle -> WeatherLightRain - WeatherIcon.Showers, - WeatherIcon.ThunderstormWithRain, - WeatherIcon.HeavyThunderstormWithRain -> WeatherRain - + WeatherIcon.LightRain, WeatherIcon.Rain -> WeatherLightRain + WeatherIcon.HeavyRain, + WeatherIcon.Thunderstorm -> WeatherRain WeatherIcon.Hail, WeatherIcon.Snow -> WeatherHail - else -> WeatherLightRain } val color = when (icon) { - WeatherIcon.Drizzle, - WeatherIcon.Showers, - WeatherIcon.ThunderstormWithRain, - WeatherIcon.HeavyThunderstormWithRain -> colors.rain + WeatherIcon.LightRain, + WeatherIcon.Rain, + WeatherIcon.HeavyRain, + WeatherIcon.Thunderstorm -> colors.rain WeatherIcon.Hail -> colors.hail WeatherIcon.Snow -> colors.snow @@ -386,7 +328,7 @@ private fun Precipitation(icon: WeatherIcon, colors: WeatherIconColors) { @Composable private fun HotCold(icon: WeatherIcon, colors: WeatherIconColors) { - if (icon == WeatherIcon.Hot) { + if (icon == WeatherIcon.ExtremeHeat) { Icon( modifier = Modifier .size(32.dp), @@ -395,7 +337,7 @@ private fun HotCold(icon: WeatherIcon, colors: WeatherIconColors) { tint = colors.hot ) } - if (icon == WeatherIcon.Cold) { + if (icon == WeatherIcon.ExtremeCold) { Icon( modifier = Modifier .size(32.dp), @@ -408,20 +350,10 @@ private fun HotCold(icon: WeatherIcon, colors: WeatherIconColors) { @Composable private fun Wind(icon: WeatherIcon, colors: WeatherIconColors) { - if (icon == WeatherIcon.Storm) { + if (icon == WeatherIcon.Wind) { Icon( modifier = Modifier - .offset(x = 4.dp, y = 0.dp) - .size(24.dp), - painter = painterResource(R.drawable.air_24px), - contentDescription = null, - tint = colors.windDark - ) - } - if (icon == WeatherIcon.Wind || icon == WeatherIcon.Storm) { - Icon( - modifier = Modifier - .size(if (icon == WeatherIcon.Wind) 32.dp else 24.dp), + .size(32.dp), painter = painterResource(R.drawable.air_24px), contentDescription = null, tint = colors.wind @@ -449,34 +381,18 @@ private fun Fog(icon: WeatherIcon, colors: WeatherIconColors) { @Composable private fun LightningBolts(icon: WeatherIcon, colors: WeatherIconColors) { - if (icon != WeatherIcon.ThunderstormWithRain - && icon != WeatherIcon.HeavyThunderstormWithRain - && icon != WeatherIcon.Thunderstorm - && icon != WeatherIcon.HeavyThunderstorm + if (icon != WeatherIcon.Thunderstorm + && icon != WeatherIcon.Thunder ) { return } - val isHeavy = - icon == WeatherIcon.HeavyThunderstormWithRain || icon == WeatherIcon.HeavyThunderstorm - Icon( modifier = Modifier .size(12.dp) - .offset(x = if (isHeavy) 4.dp else 1.dp, y = 6.dp), + .offset(x = 1.dp, y = 6.dp), painter = painterResource(R.drawable.bolt_24px_filled), contentDescription = null, tint = colors.lightningBolt ) - - if (icon == WeatherIcon.HeavyThunderstorm || icon == WeatherIcon.HeavyThunderstormWithRain) { - Icon( - modifier = Modifier - .size(10.dp) - .offset(x = -3.dp, y = 6.dp), - painter = painterResource(R.drawable.bolt_24px_filled), - contentDescription = null, - tint = colors.lightningBolt - ) - } } \ No newline at end of file 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 4b2aa9d36..932fc9732 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 @@ -1,8 +1,6 @@ package de.mm20.launcher2.ui.component.weather -import android.content.Context import androidx.annotation.ColorInt -import androidx.annotation.ColorRes import androidx.compose.material3.MaterialTheme import androidx.compose.runtime.Composable import androidx.compose.runtime.remember @@ -10,9 +8,9 @@ import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.toArgb import androidx.compose.ui.platform.LocalContext import blend.Blend -import de.mm20.launcher2.ui.R import de.mm20.launcher2.ui.ktx.atTone import de.mm20.launcher2.ui.locals.LocalDarkTheme +import hct.Hct data class WeatherIconColors( @@ -38,38 +36,138 @@ data class WeatherIconColors( object WeatherIconDefaults { @Composable fun colors(): WeatherIconColors { + val darkTheme = LocalDarkTheme.current + + if (darkTheme) { + return darkColors() + } else { + return lightColors() + } + } + + @Composable + fun invertedColors(): WeatherIconColors { + val darkTheme = LocalDarkTheme.current + + if (!darkTheme) { + return darkColors() + } else { + return lightColors() + } + } + + @Composable + fun darkColors(): WeatherIconColors { val context = LocalContext.current val themeColor = MaterialTheme.colorScheme.primary - val darkTheme = LocalDarkTheme.current val neutral1 = MaterialTheme.colorScheme.outline val neutral2 = MaterialTheme.colorScheme.outline return remember(themeColor) { WeatherIconColors( - sun = harmonize(context, 0xFFFFB300.toInt(), themeColor), - moon = harmonize(context, 0xFF9E9E9E.toInt(), themeColor), - cloudDark1 = harmonize(context, neutral1.atTone(if (darkTheme) 40 else 30).toArgb(), themeColor), - cloudDark2 = harmonize(context, neutral1.atTone(if (darkTheme) 30 else 20).toArgb(), themeColor), - cloudMedium1 = harmonize(context, neutral1.atTone(if (darkTheme) 60 else 50).toArgb(), themeColor), - cloudMedium2 = harmonize(context, neutral1.atTone(if (darkTheme) 50 else 40).toArgb(), themeColor), - cloudLight1 = harmonize(context, neutral1.atTone(if (darkTheme) 95 else 85).toArgb(), themeColor), - cloudLight2 = harmonize(context, neutral1.atTone(if (darkTheme) 85 else 75).toArgb(), themeColor), - rain = harmonize(context, if (darkTheme) 0xFF64B5F6.toInt() else 0xFF1E88E5.toInt(), themeColor), - snow = harmonize(context, if (darkTheme) 0xFFF5F5F5.toInt() else 0xFFE0E0E0.toInt(), themeColor), - hail = harmonize(context, if (darkTheme) 0xFFF5F5F5.toInt() else 0xFFE0E0E0.toInt(), themeColor), - fog = harmonize(context, neutral1.atTone(if (darkTheme) 95 else 85).toArgb(), themeColor), - wind = harmonize(context, neutral2.atTone(if (darkTheme) 70 else 75).toArgb(), themeColor), - windDark = harmonize(context, neutral2.atTone(if (darkTheme) 40 else 45).toArgb(), themeColor), - lightningBolt = harmonize(context, 0xFFFFB300.toInt(), themeColor), - hot = harmonize(context, if (darkTheme) 0xFFE57373.toInt() else 0xFFE53935.toInt(), themeColor), - cold = harmonize(context, if (darkTheme) 0xFF4FC3F7.toInt() else 0xFF039BE5.toInt(), themeColor), + sun = harmonize(0xFFFFB300.toInt(), themeColor), + moon = harmonize(0xFF9E9E9E.toInt(), themeColor), + cloudDark1 = harmonize(neutral1.atTone(40).toArgb(), themeColor), + cloudDark2 = harmonize(neutral1.atTone(30).toArgb(), themeColor), + cloudMedium1 = harmonize(neutral1.atTone(60).toArgb(), themeColor), + cloudMedium2 = harmonize(neutral1.atTone(50).toArgb(), themeColor), + cloudLight1 = harmonize(neutral1.atTone(95).toArgb(), themeColor), + cloudLight2 = harmonize(neutral1.atTone(85).toArgb(), themeColor), + rain = harmonize(0xFF64B5F6.toInt(), themeColor), + snow = harmonize(0xFFF5F5F5.toInt(), themeColor), + hail = harmonize(0xFFF5F5F5.toInt(), themeColor), + fog = harmonize(neutral1.atTone(95).toArgb(), themeColor), + wind = harmonize(neutral2.atTone(70).toArgb(), themeColor), + windDark = harmonize(neutral2.atTone(40).toArgb(), themeColor), + lightningBolt = harmonize(0xFFFFB300.toInt(), themeColor), + hot = harmonize(0xFFE57373.toInt(), themeColor), + cold = harmonize(0xFF4FC3F7.toInt(), themeColor), + ) + } + } + + @Composable + fun lightColors(): WeatherIconColors { + val context = LocalContext.current + + val themeColor = MaterialTheme.colorScheme.primary + val neutral1 = MaterialTheme.colorScheme.outline + val neutral2 = MaterialTheme.colorScheme.outline + + return remember(themeColor) { + WeatherIconColors( + sun = harmonize(0xFFFFB300.toInt(), themeColor), + moon = harmonize(0xFF9E9E9E.toInt(), themeColor), + cloudDark1 = harmonize(neutral1.atTone(30).toArgb(), themeColor), + cloudDark2 = harmonize(neutral1.atTone(20).toArgb(), themeColor), + cloudMedium1 = harmonize(neutral1.atTone(50).toArgb(), themeColor), + cloudMedium2 = harmonize(neutral1.atTone(40).toArgb(), themeColor), + cloudLight1 = harmonize(neutral1.atTone(85).toArgb(), themeColor), + cloudLight2 = harmonize(neutral1.atTone(75).toArgb(), themeColor), + rain = harmonize(0xFF1E88E5.toInt(), themeColor), + snow = harmonize(0xFFE0E0E0.toInt(), themeColor), + hail = harmonize(0xFFE0E0E0.toInt(), themeColor), + fog = harmonize(neutral1.atTone(85).toArgb(), themeColor), + wind = harmonize(neutral2.atTone(75).toArgb(), themeColor), + windDark = harmonize(neutral2.atTone(45).toArgb(), themeColor), + lightningBolt = harmonize(0xFFFFB300.toInt(), themeColor), + hot = harmonize(0xFFE53935.toInt(), themeColor), + cold = harmonize(0xFF039BE5.toInt(), themeColor), + ) + } + } + + @Composable + fun monochromeColors(baseColor: Color): WeatherIconColors { + val baseTone = Hct.fromInt(baseColor.toArgb()).tone.toInt() + + return remember(baseColor) { + /** + * Adjusts the tone of a color. + * The tone is adjusted up or down to not overlap with the base color's tone. + * + * @param tone The tone to adjust. + * @return The adjusted tone. + */ + fun t(tone: Int): Int { + val upper = (baseTone + 15f).coerceAtMost(100f) + val lower = (baseTone - 15f).coerceAtLeast(0f) + val diff = upper - lower + val scale = (100f - diff) / 100f + + val tn = tone * scale + + if (tn > lower) { + return (tn + diff).toInt() + } + return tn.toInt() + } + + WeatherIconColors( + sun = baseColor.atTone(t(90)), + moon = baseColor.atTone(t(90)), + cloudDark1 = baseColor.atTone(t(25)), + cloudDark2 = baseColor.atTone(t(15)), + cloudMedium1 = baseColor.atTone(t(45)), + cloudMedium2 = baseColor.atTone(t(35)), + cloudLight1 = baseColor.atTone(t(65)), + cloudLight2 = baseColor.atTone(t(55)), + rain = baseColor.atTone(t(55)), + snow = baseColor.atTone(t(100)), + hail = baseColor.atTone(t(90)), + fog = baseColor.atTone(t(60)), + wind = baseColor.atTone(t(65)), + windDark = baseColor.atTone(t(45)), + lightningBolt = baseColor.atTone(t(90)), + hot = baseColor.atTone(t(55)), + cold = baseColor.atTone(t(55)), ) } } } -private fun harmonize(context: Context, @ColorInt baseColor: Int, themeColor: Color): Color { +private fun harmonize(@ColorInt baseColor: Int, themeColor: Color): Color { return Color(Blend.harmonize(baseColor, themeColor.toArgb())) } \ No newline at end of file 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 bec7df144..fad88ba33 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 @@ -6,12 +6,14 @@ import android.content.Intent import android.net.Uri import android.text.format.DateUtils import androidx.appcompat.app.AppCompatActivity +import androidx.compose.animation.AnimatedContent import androidx.compose.animation.AnimatedVisibility import androidx.compose.animation.core.animateFloatAsState import androidx.compose.animation.expandHorizontally import androidx.compose.animation.fadeIn import androidx.compose.animation.fadeOut import androidx.compose.animation.shrinkHorizontally +import androidx.compose.animation.togetherWith import androidx.compose.foundation.LocalIndication import androidx.compose.foundation.background import androidx.compose.foundation.clickable @@ -79,6 +81,8 @@ 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 +import de.mm20.launcher2.ui.ktx.animateShapeAsState import de.mm20.launcher2.ui.locals.LocalMeasurementSystem import de.mm20.launcher2.ui.locals.LocalTimeFormat import de.mm20.launcher2.ui.theme.transparency.transparency @@ -187,21 +191,32 @@ fun WeatherWidget(widget: WeatherWidget) { Surface( color = MaterialTheme.colorScheme.surfaceContainer.copy(alpha = MaterialTheme.transparency.surface), - modifier = Modifier.fillMaxWidth() + modifier = Modifier + .fillMaxWidth() + .padding(8.dp), + shape = MaterialTheme.shapes.small, ) { Column( - modifier = Modifier.padding(top = 12.dp, bottom = 12.dp) + modifier = Modifier.padding(top = 8.dp, bottom = 8.dp) ) { - WeatherTimeSelector( - forecasts = currentDayForecasts, - selectedForecast = forecast, - measurementSystem = measurementSystem, - timeFormat = timeFormat, - onTimeSelected = { - viewModel.selectForecast(it) - }, - modifier = Modifier.padding(bottom = 12.dp) - ) + AnimatedContent( + currentDayForecasts to forecast, + contentKey = { it.first }, + transitionSpec = { + fadeIn() togetherWith fadeOut() + } + ) { (fcs, fc) -> + WeatherTimeSelector( + forecasts = fcs, + selectedForecast = fc, + measurementSystem = measurementSystem, + timeFormat = timeFormat, + onTimeSelected = { + viewModel.selectForecast(it) + }, + modifier = Modifier.padding(bottom = 8.dp) + ) + } selectedDayForecast?.let { WeatherDaySelector( days = dailyForecasts, @@ -469,12 +484,16 @@ fun WeatherTimeSelector( val context = LocalContext.current val listState = rememberLazyListState() + + val colors = WeatherIconDefaults.colors() + val selectedColors = WeatherIconDefaults.monochromeColors(MaterialTheme.colorScheme.secondary) + LazyRow( state = listState, modifier = modifier .fillMaxWidth(), horizontalArrangement = Arrangement.spacedBy(2.dp), - contentPadding = PaddingValues(start = 12.dp, end = 12.dp), + contentPadding = PaddingValues(start = 8.dp, end = 8.dp), verticalAlignment = Alignment.CenterVertically ) { itemsIndexed(forecasts, key = { idx, _ -> idx }) { idx, fc -> @@ -482,22 +501,26 @@ fun WeatherTimeSelector( val sm = MaterialTheme.shapes.small val xs = MaterialTheme.shapes.extraSmall Surface( - shape = when (idx) { - 0 -> xs.copy( - topStart = sm.topStart, - bottomStart = sm.bottomStart - ) + shape = animateShapeAsState( + when { + selected -> sm + idx == 0 -> xs.copy( + topStart = sm.topStart, + bottomStart = sm.bottomStart + ) - forecasts.lastIndex -> xs.copy( - topEnd = sm.topEnd, - bottomEnd = sm.bottomEnd - ) + idx == forecasts.lastIndex -> xs.copy( + topEnd = sm.topEnd, + bottomEnd = sm.bottomEnd + ) - else -> MaterialTheme.shapes.extraSmall - }, + else -> MaterialTheme.shapes.extraSmall + } + ).value, modifier = Modifier .widthIn(min = 60.dp), - color = MaterialTheme.colorScheme.surface, + color = if (selected) MaterialTheme.colorScheme.secondary else MaterialTheme.colorScheme.surfaceBright, + contentColor = if (selected) MaterialTheme.colorScheme.onSecondary else MaterialTheme.colorScheme.onSurfaceVariant, ) { Column( modifier = Modifier @@ -513,12 +536,12 @@ fun WeatherTimeSelector( } .padding(bottom = 4.dp), icon = weatherIconById(fc.icon), - night = fc.night + night = fc.night, + colors = if (selected) selectedColors else colors ) Text( text = formatTime(context, fc.timestamp, timeFormat), style = MaterialTheme.typography.labelSmall, - color = MaterialTheme.colorScheme.onSurfaceVariant, softWrap = false, modifier = Modifier.align(Alignment.CenterHorizontally) ) @@ -536,13 +559,15 @@ fun WeatherTimeSelector( ), softWrap = false, style = MaterialTheme.typography.labelSmall, - color = MaterialTheme.colorScheme.onSurfaceVariant, ) Box( modifier = Modifier .alpha(1f - alpha) .requiredSize(8.dp) - .background(MaterialTheme.colorScheme.primary, CircleShape) + .background( + MaterialTheme.colorScheme.onSecondary, + CircleShape + ) ) } } @@ -562,6 +587,9 @@ fun WeatherDaySelector( val dateFormat = SimpleDateFormat("EEE") val context = LocalContext.current + val colors = WeatherIconDefaults.colors() + val selectedColors = WeatherIconDefaults.monochromeColors(MaterialTheme.colorScheme.secondary) + val listState = rememberLazyListState() LazyRow( state = listState, @@ -569,7 +597,7 @@ fun WeatherDaySelector( .fillMaxWidth(), horizontalArrangement = Arrangement.spacedBy(2.dp), verticalAlignment = Alignment.CenterVertically, - contentPadding = PaddingValues(start = 12.dp, end = 12.dp), + contentPadding = PaddingValues(start = 8.dp, end = 8.dp), ) { itemsIndexed(days, key = { idx, _ -> idx }) { idx, day -> val selected = day == selectedDay @@ -577,20 +605,24 @@ fun WeatherDaySelector( val sm = MaterialTheme.shapes.small val xs = MaterialTheme.shapes.extraSmall Surface( - shape = when (idx) { - 0 -> xs.copy( - topStart = sm.topStart, - bottomStart = sm.bottomStart - ) + shape = animateShapeAsState( + when { + selected -> sm + idx == 0 -> xs.copy( + topStart = sm.topStart, + bottomStart = sm.bottomStart + ) - days.lastIndex -> xs.copy( - topEnd = sm.topEnd, - bottomEnd = sm.bottomEnd - ) + idx == days.lastIndex -> xs.copy( + topEnd = sm.topEnd, + bottomEnd = sm.bottomEnd + ) - else -> MaterialTheme.shapes.extraSmall - }, - color = MaterialTheme.colorScheme.surface, + else -> MaterialTheme.shapes.extraSmall + } + ).value, + color = if (selected) MaterialTheme.colorScheme.secondary else MaterialTheme.colorScheme.surfaceBright, + contentColor = if (selected) MaterialTheme.colorScheme.onSecondary else MaterialTheme.colorScheme.onSurfaceVariant, ) { Row( modifier = Modifier @@ -598,7 +630,10 @@ fun WeatherDaySelector( .padding(top = 4.dp, bottom = 4.dp, start = 4.dp, end = 8.dp), verticalAlignment = Alignment.CenterVertically ) { - WeatherIcon(icon = weatherIconById(day.icon)) + WeatherIcon( + icon = weatherIconById(day.icon), + colors = if (selected) selectedColors else colors + ) Text( modifier = Modifier.padding(start = 8.dp), text = "${dateFormat.format(day.timestamp)} " + @@ -616,7 +651,6 @@ fun WeatherDaySelector( ), softWrap = false, style = MaterialTheme.typography.labelSmall, - color = MaterialTheme.colorScheme.onSurfaceVariant, ) val spec = MaterialTheme.motionScheme.fastSpatialSpec() AnimatedVisibility( @@ -628,7 +662,10 @@ fun WeatherDaySelector( modifier = Modifier .padding(start = 8.dp) .size(8.dp) - .background(MaterialTheme.colorScheme.primary, CircleShape) + .background( + MaterialTheme.colorScheme.onSecondary, + CircleShape + ) ) } } @@ -678,26 +715,27 @@ private fun windDirectionAsWord(direction: Double): String { private fun weatherIconById(id: Int): WeatherIcon { return when (id) { Forecast.CLEAR -> WeatherIcon.Clear - Forecast.CLOUDY -> WeatherIcon.Cloudy - Forecast.COLD -> WeatherIcon.Cold - Forecast.DRIZZLE -> WeatherIcon.Drizzle + Forecast.OVERCAST -> WeatherIcon.Overcast + Forecast.EXTREME_COLD -> WeatherIcon.ExtremeHeat + Forecast.LIGHT_RAIN -> WeatherIcon.LightRain Forecast.HAZE -> WeatherIcon.Haze Forecast.FOG -> WeatherIcon.Fog Forecast.HAIL -> WeatherIcon.Hail - Forecast.HEAVY_THUNDERSTORM -> WeatherIcon.HeavyThunderstorm - Forecast.HEAVY_THUNDERSTORM_WITH_RAIN -> WeatherIcon.HeavyThunderstormWithRain - Forecast.HOT -> WeatherIcon.Hot - Forecast.MOSTLY_CLOUDY -> WeatherIcon.MostlyCloudy + Forecast.EXTREME_HEAT -> WeatherIcon.ExtremeHeat Forecast.PARTLY_CLOUDY -> WeatherIcon.PartlyCloudy - Forecast.SHOWERS -> WeatherIcon.Showers + Forecast.RAIN -> WeatherIcon.Rain + Forecast.HEAVY_RAIN -> WeatherIcon.HeavyRain Forecast.SLEET -> WeatherIcon.Sleet Forecast.SNOW -> WeatherIcon.Snow - Forecast.STORM -> WeatherIcon.Storm + Forecast.THUNDER -> WeatherIcon.Thunder Forecast.THUNDERSTORM -> WeatherIcon.Thunderstorm - Forecast.THUNDERSTORM_WITH_RAIN -> WeatherIcon.ThunderstormWithRain Forecast.WIND -> WeatherIcon.Wind - Forecast.BROKEN_CLOUDS -> WeatherIcon.BrokenClouds - else -> WeatherIcon.None + Forecast.MOSTLY_CLOUDY -> WeatherIcon.PartlyCloudy + Forecast.STORM -> WeatherIcon.Wind + Forecast.BROKEN_CLOUDS -> WeatherIcon.PartlyCloudy + Forecast.HEAVY_THUNDERSTORM -> WeatherIcon.Thunder + Forecast.HEAVY_THUNDERSTORM_WITH_RAIN -> WeatherIcon.Thunderstorm + else -> WeatherIcon.Unknown } } diff --git a/core/shared/src/main/java/de/mm20/launcher2/weather/WeatherIcon.kt b/core/shared/src/main/java/de/mm20/launcher2/weather/WeatherIcon.kt index ef190e88a..787ba234d 100644 --- a/core/shared/src/main/java/de/mm20/launcher2/weather/WeatherIcon.kt +++ b/core/shared/src/main/java/de/mm20/launcher2/weather/WeatherIcon.kt @@ -3,23 +3,42 @@ package de.mm20.launcher2.weather enum class WeatherIcon(val id: Int) { Unknown(-1), Clear(0), - Cloudy(1), - Cold(2), - Drizzle(3), + Overcast(1), + ExtremeCold(2), Haze(4), Fog(5), Hail(6), - HeavyThunderstorm(7), - HeavyThunderstormWithRain(8), - Hot(9), - MostlyCloudy(10), + ExtremeHeat(9), PartlyCloudy(11), - Showers(12), Sleet(13), Snow(14), - Storm(15), - Thunderstorm(16), - ThunderstormWithRain(17), + Thunder(16), + Thunderstorm(17), Wind(18), + Rain(12), + LightRain(3), + HeavyRain(20), + + @Deprecated("Deprecated", ReplaceWith("WeatherIcon.PartlyCloudy")) BrokenClouds(19), + @Deprecated("Deprecated", ReplaceWith("WeatherIcon.Thunder")) + HeavyThunderstorm(7), + @Deprecated("Deprecated", ReplaceWith("WeatherIcon.Thunderstorm")) + HeavyThunderstormWithRain(8), + @Deprecated("Deprecated", ReplaceWith("WeatherIcon.Thunderstorm")) + ThunderstormWithRain(17), + @Deprecated("Deprecated", ReplaceWith("WeatherIcon.Rain")) + Showers(12), + @Deprecated("Deprecated", ReplaceWith("WeatherIcon.LightRain")) + Drizzle(3), + @Deprecated("Deprecated", ReplaceWith("WeatherIcon.PartlyCloudy")) + MostlyCloudy(10), + @Deprecated("Deprecated", ReplaceWith("WeatherIcon.Overcast")) + Cloudy(1), + @Deprecated("Deprecated", ReplaceWith("WeatherIcon.ExtremeCold")) + Cold(2), + @Deprecated("Deprecated", ReplaceWith("WeatherIcon.ExtremeHeat")) + Hot(9), + @Deprecated("Deprecated", ReplaceWith("WeatherIcon.Wind")) + Storm(15), } \ No newline at end of file diff --git a/data/weather/src/main/java/de/mm20/launcher2/weather/DailyForecast.kt b/data/weather/src/main/java/de/mm20/launcher2/weather/DailyForecast.kt index dca2a162d..6d43b5f82 100644 --- a/data/weather/src/main/java/de/mm20/launcher2/weather/DailyForecast.kt +++ b/data/weather/src/main/java/de/mm20/launcher2/weather/DailyForecast.kt @@ -1,121 +1,128 @@ package de.mm20.launcher2.weather -import kotlin.math.abs - data class DailyForecast( - val timestamp: Long, - val minTemp: Double, - val maxTemp: Double, - val hourlyForecasts: List, - val icon: Int = getAverageIcon(hourlyForecasts) + val timestamp: Long, + val minTemp: Double, + val maxTemp: Double, + val hourlyForecasts: List, + val icon: Int = getAverageIcon(hourlyForecasts) ) { companion object { private fun getAverageIcon(forecasts: List): Int { + if (forecasts.size == 1) { + return forecasts[0].icon + } + var clear = 0f var clouds = 0f var rain = 0f var thunder = 0f var wind = 0f var snow = 0f - for (f in forecasts) { - when (f.icon) { - Forecast.SHOWERS, Forecast.HAIL -> { - rain += 2f - clouds += 1f - } - Forecast.THUNDERSTORM_WITH_RAIN -> { - rain += 2f - thunder += 5f - clouds += 1f - } - Forecast.THUNDERSTORM -> { - thunder += 5f - clouds += 1f - } - Forecast.BROKEN_CLOUDS, Forecast.MOSTLY_CLOUDY -> { - clouds += 0.7f - clear += 0.3f - } + var hail = 0f + var precipitation = 0f + var total = 0f + + for (fc in forecasts) { + val f = if (fc.night) 0.8f else 1f + when (fc.icon) { + Forecast.CLEAR -> clear += f + Forecast.OVERCAST -> clouds += f Forecast.PARTLY_CLOUDY -> { - clouds += 0.3f - clear += 0.7f + clouds += f * 0.5f + clear += f * 0.5f } - Forecast.CLOUDY -> { - clouds += 1f + + Forecast.RAIN -> { + rain += f + clouds += f + precipitation += f } + + Forecast.HEAVY_RAIN -> { + rain += f * 1.5f + clouds += f + precipitation += f + } + + Forecast.LIGHT_RAIN -> { + rain += f * 0.75f + clouds += f + precipitation += f + } + Forecast.SNOW -> { - snow += 2f - clouds += 1f - } - Forecast.DRIZZLE -> { - rain += 1f - } - Forecast.HEAVY_THUNDERSTORM -> { - thunder += 8f - } - Forecast.HEAVY_THUNDERSTORM_WITH_RAIN -> { - thunder += 8f - clouds += 1f + snow += f + clouds += f + precipitation += f } + Forecast.SLEET -> { - rain += 1f - snow += 1f + snow += f * 0.5f + rain += f * 0.5f + clouds += f + precipitation += f } - Forecast.STORM -> { - wind += 8f + + Forecast.HAIL -> { + hail += f + clouds += f + precipitation += f } + + Forecast.THUNDER -> { + thunder += f + clouds += f + } + + Forecast.THUNDERSTORM -> { + thunder += f + rain += f * 1.5f + clouds += f + precipitation += f + } + Forecast.WIND -> { - wind += 5f - } - Forecast.CLEAR -> { - clear += 1f + wind += f + clouds += f } + + + } + total += f + } + + if (wind / total >= 0.05f) { + return Forecast.WIND + } + + + if (precipitation / total >= 0.3f) { + return when { + hail > snow && hail > rain -> Forecast.HAIL + snow > rain && snow / rain > 2f -> Forecast.SNOW + snow / rain <= 2f && rain / snow <= 2f -> Forecast.SLEET + thunder > 0f -> Forecast.THUNDERSTORM + (rain + snow + hail) / precipitation >= 1.25f -> Forecast.HEAVY_RAIN + (rain + snow + hail) / precipitation <= 0.8725f -> Forecast.LIGHT_RAIN + else -> Forecast.RAIN } } - val pairs = listOf( - "clear" to clear, - "clouds" to clouds, - "rain" to rain, - "thunder" to thunder, - "wind" to wind, - "snow" to snow - ).sortedByDescending { it.second } - val first = pairs[0] - val second = pairs[1] - when (first.first) { - "wind" -> return if (first.second / forecasts.size > 6f) Forecast.STORM else Forecast.WIND - "thunder" -> { - val heavy = first.second / forecasts.size > 6f - val withRain = second.first == "rain" - if (heavy && withRain) return Forecast.HEAVY_THUNDERSTORM_WITH_RAIN - if (heavy && !withRain) return Forecast.HEAVY_THUNDERSTORM - if (!heavy && withRain) return Forecast.THUNDERSTORM_WITH_RAIN - return Forecast.THUNDERSTORM - } - "rain" -> { - val heavy = first.second / forecasts.size > 0.8f - val withSnow = second.first == "snow" && abs(1 - (first.second / second.second)) < 0.2 - if (withSnow) return Forecast.SLEET - if (heavy) return Forecast.SHOWERS - return Forecast.DRIZZLE - } - "snow" -> { - val withRain = second.first == "rain" && abs(1 - (first.second / second.second)) < 0.2 - if (withRain) return Forecast.SLEET - return Forecast.SNOW - } - else -> { - if (clouds == 0f) return Forecast.CLEAR - if (clear == 0f) return Forecast.CLOUDY - if (clouds > clear) { - if (clear > snow && clear > rain) return Forecast.MOSTLY_CLOUDY - if (clear < snow && clear < rain) return Forecast.SLEET - if (clear < snow && clear > rain) return Forecast.SNOW - if (clear > snow && clear < rain) return Forecast.DRIZZLE - } - return Forecast.PARTLY_CLOUDY - } + + if (thunder / total >= 0.05f) { + return Forecast.THUNDER } + + if (clear / clouds < 0.2f) { + return Forecast.OVERCAST + } + + if (clouds / clear < 0.1f) { + return Forecast.CLEAR + } + + return Forecast.PARTLY_CLOUDY + } } } \ No newline at end of file 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 819fdd14f..d5b08c79f 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 @@ -1,135 +1,143 @@ package de.mm20.launcher2.weather import de.mm20.launcher2.database.entities.ForecastEntity -import de.mm20.launcher2.weather.Forecast.Companion.BROKEN_CLOUDS import de.mm20.launcher2.weather.Forecast.Companion.CLEAR -import de.mm20.launcher2.weather.Forecast.Companion.CLOUDY -import de.mm20.launcher2.weather.Forecast.Companion.COLD -import de.mm20.launcher2.weather.Forecast.Companion.DRIZZLE +import de.mm20.launcher2.weather.Forecast.Companion.EXTREME_COLD +import de.mm20.launcher2.weather.Forecast.Companion.EXTREME_HEAT import de.mm20.launcher2.weather.Forecast.Companion.FOG import de.mm20.launcher2.weather.Forecast.Companion.HAIL import de.mm20.launcher2.weather.Forecast.Companion.HAZE -import de.mm20.launcher2.weather.Forecast.Companion.HEAVY_THUNDERSTORM -import de.mm20.launcher2.weather.Forecast.Companion.HEAVY_THUNDERSTORM_WITH_RAIN -import de.mm20.launcher2.weather.Forecast.Companion.HOT -import de.mm20.launcher2.weather.Forecast.Companion.MOSTLY_CLOUDY -import de.mm20.launcher2.weather.Forecast.Companion.NONE +import de.mm20.launcher2.weather.Forecast.Companion.HEAVY_RAIN +import de.mm20.launcher2.weather.Forecast.Companion.LIGHT_RAIN +import de.mm20.launcher2.weather.Forecast.Companion.OVERCAST import de.mm20.launcher2.weather.Forecast.Companion.PARTLY_CLOUDY -import de.mm20.launcher2.weather.Forecast.Companion.SHOWERS +import de.mm20.launcher2.weather.Forecast.Companion.RAIN import de.mm20.launcher2.weather.Forecast.Companion.SLEET import de.mm20.launcher2.weather.Forecast.Companion.SNOW -import de.mm20.launcher2.weather.Forecast.Companion.STORM +import de.mm20.launcher2.weather.Forecast.Companion.THUNDER import de.mm20.launcher2.weather.Forecast.Companion.THUNDERSTORM -import de.mm20.launcher2.weather.Forecast.Companion.THUNDERSTORM_WITH_RAIN +import de.mm20.launcher2.weather.Forecast.Companion.UNKNOWN import de.mm20.launcher2.weather.Forecast.Companion.WIND data class Forecast( - val timestamp: Long, - /** The temperature, in Kelvin **/ - val temperature: Double, - /** The min temperature, in Kelvin **/ - val minTemp: Double? = null, - /** The max temperature, in Kelvin **/ - val maxTemp: Double? = null, - /** The temperature, in hPa **/ - val pressure: Double? = null, - /** The temperature, in percent **/ - val humidity: Double? = null, - /** The icon, one of [NONE], [CLEAR], [CLOUDY], [COLD], [DRIZZLE], [HAZE], [FOG], - * [HAIL], [HEAVY_THUNDERSTORM], [HEAVY_THUNDERSTORM_WITH_RAIN], [HOT], [MOSTLY_CLOUDY], - * [PARTLY_CLOUDY], [SHOWERS], [SLEET], [SNOW], [STORM], [THUNDERSTORM], - * [THUNDERSTORM_WITH_RAIN], [WIND], [BROKEN_CLOUDS]**/ - val icon: Int, - /** A text describing the current weather condition **/ - val condition: String, - /** The clouds, percentage **/ - val clouds: Int? = null, - /** Wind speed, in m/s **/ - val windSpeed: Double? = null, - /** wind direction, in degrees **/ - val windDirection: Double? = null, - /** rain, in mm per hour **/ - val precipitation: Double? = null, - /** whether this forecast is during night time (whether a moon icon should be used instead of sun) **/ - val night: Boolean = false, - /** Location string **/ - val location: String, - /** Provider name **/ - val provider: String, - /** Url to the provider and more weather information **/ - val providerUrl: String = "", - /** Rain probability, in percent [0..100]. null if not available **/ - val precipProbability: Int? = null, - /** Timestamp (in millis) when when this forecast was created **/ - val updateTime: Long + val timestamp: Long, + /** The temperature, in Kelvin **/ + val temperature: Double, + /** The min temperature, in Kelvin **/ + val minTemp: Double? = null, + /** The max temperature, in Kelvin **/ + val maxTemp: Double? = null, + /** The temperature, in hPa **/ + val pressure: Double? = null, + /** The temperature, in percent **/ + val humidity: Double? = null, + /** The icon, one of [UNKNOWN], [CLEAR], [OVERCAST], [EXTREME_COLD], [LIGHT_RAIN], [HAZE], + * [FOG], [HAIL], [EXTREME_HEAT], [PARTLY_CLOUDY], [RAIN], [HEAVY_RAIN] [SLEET], [SNOW], + * [THUNDER], [THUNDERSTORM], [WIND]**/ + val icon: Int, + /** A text describing the current weather condition **/ + val condition: String, + /** The clouds, percentage **/ + val clouds: Int? = null, + /** Wind speed, in m/s **/ + val windSpeed: Double? = null, + /** wind direction, in degrees **/ + val windDirection: Double? = null, + /** rain, in mm per hour **/ + val precipitation: Double? = null, + /** whether this forecast is during nighttime (whether a moon icon should be used instead of sun) **/ + val night: Boolean = false, + /** Location string **/ + val location: String, + /** Provider name **/ + val provider: String, + /** Url to the provider and more weather information **/ + val providerUrl: String = "", + /** Rain probability, in percent [0..100]. null if not available **/ + val precipProbability: Int? = null, + /** Timestamp (in millis) when this forecast was created **/ + val updateTime: Long ) { fun toDatabaseEntity(): ForecastEntity { return ForecastEntity( - timestamp = timestamp, - clouds = clouds ?: -1, - condition = condition, - humidity = humidity ?: -1.0, - icon = icon, - location = location, - maxTemp = maxTemp ?: -1.0, - minTemp = minTemp ?: -1.0, - night = night, - pressure = pressure ?: -1.0, - provider = provider, - providerUrl = providerUrl, - precipitation = precipitation ?: -1.0, - precipProbability = precipProbability ?: -1, - temperature = temperature, - updateTime = updateTime, - windDirection = windDirection ?: -1.0, - windSpeed = windSpeed ?: -1.0, - snow = -1.0, - snowProbability = -1 + timestamp = timestamp, + clouds = clouds ?: -1, + condition = condition, + humidity = humidity ?: -1.0, + icon = icon, + location = location, + maxTemp = maxTemp ?: -1.0, + minTemp = minTemp ?: -1.0, + night = night, + pressure = pressure ?: -1.0, + provider = provider, + providerUrl = providerUrl, + precipitation = precipitation ?: -1.0, + precipProbability = precipProbability ?: -1, + temperature = temperature, + updateTime = updateTime, + windDirection = windDirection ?: -1.0, + windSpeed = windSpeed ?: -1.0, + snow = -1.0, + snowProbability = -1 ) } constructor(entity: ForecastEntity) : this( - timestamp = entity.timestamp, - clouds = entity.clouds.takeIf { it >= 0 }, - condition = entity.condition, - humidity = entity.humidity.takeIf { it >= 0.0 }, - icon = entity.icon, - location = entity.location, - maxTemp = entity.maxTemp.takeIf { it >= 0.0 }, - minTemp = entity.minTemp.takeIf { it >= 0.0 }, - night = entity.night, - pressure = entity.pressure.takeIf { it >= 0.0 }, - provider = entity.provider, - providerUrl = entity.providerUrl, - precipitation = entity.precipitation.takeIf { it >= 0.0 }, - precipProbability = entity.precipProbability.takeIf { it >= 0 }, - temperature = entity.temperature, - updateTime = entity.updateTime, - windDirection = entity.windDirection.takeIf { it >= 0.0 }, - windSpeed = entity.windSpeed.takeIf { it >= 0.0 }, + timestamp = entity.timestamp, + clouds = entity.clouds.takeIf { it >= 0 }, + condition = entity.condition, + humidity = entity.humidity.takeIf { it >= 0.0 }, + icon = entity.icon, + location = entity.location, + maxTemp = entity.maxTemp.takeIf { it >= 0.0 }, + minTemp = entity.minTemp.takeIf { it >= 0.0 }, + night = entity.night, + pressure = entity.pressure.takeIf { it >= 0.0 }, + provider = entity.provider, + providerUrl = entity.providerUrl, + precipitation = entity.precipitation.takeIf { it >= 0.0 }, + precipProbability = entity.precipProbability.takeIf { it >= 0 }, + temperature = entity.temperature, + updateTime = entity.updateTime, + windDirection = entity.windDirection.takeIf { it >= 0.0 }, + windSpeed = entity.windSpeed.takeIf { it >= 0.0 }, ) companion object { - const val NONE = -1 + const val UNKNOWN = -1 const val CLEAR = 0 - const val CLOUDY = 1 - const val COLD = 2 - const val DRIZZLE = 3 + const val OVERCAST = 1 + const val EXTREME_COLD = 2 + const val LIGHT_RAIN = 3 const val HAZE = 4 const val FOG = 5 const val HAIL = 6 - const val HEAVY_THUNDERSTORM = 7 - const val HEAVY_THUNDERSTORM_WITH_RAIN = 8 - const val HOT = 9 - const val MOSTLY_CLOUDY = 10 + const val EXTREME_HEAT = 9 + const val PARTLY_CLOUDY = 11 - const val SHOWERS = 12 + const val RAIN = 12 const val SLEET = 13 const val SNOW = 14 - const val STORM = 15 - const val THUNDERSTORM = 16 - const val THUNDERSTORM_WITH_RAIN = 17 + const val THUNDER = 16 + const val THUNDERSTORM = 17 const val WIND = 18 + const val HEAVY_RAIN = 20 + + @Deprecated("Deprecated", ReplaceWith("THUNDER")) + const val HEAVY_THUNDERSTORM = 7 + + @Deprecated("Deprecated", ReplaceWith("THUNDERSTORM")) + const val HEAVY_THUNDERSTORM_WITH_RAIN = 8 + + @Deprecated("Deprecated", ReplaceWith("PARTLY_CLOUDY")) + const val MOSTLY_CLOUDY = 10 + + @Deprecated("Deprecated", ReplaceWith("WIND")) + const val STORM = 15 + + @Deprecated("Deprecated", ReplaceWith("PARTLY_CLOUDY")) const val BROKEN_CLOUDS = 19 + } } \ No newline at end of file diff --git a/data/weather/src/main/java/de/mm20/launcher2/weather/Weather.kt b/data/weather/src/main/java/de/mm20/launcher2/weather/Weather.kt deleted file mode 100644 index e509a0b0b..000000000 --- a/data/weather/src/main/java/de/mm20/launcher2/weather/Weather.kt +++ /dev/null @@ -1,47 +0,0 @@ -package de.mm20.launcher2.weather - -data class Weather2( - val timestamp: Long, - val temperature: Double, - val minTemp: Double, - val maxTemp: Double, - val pressure: Double, - val humidity: Double, - val icon: Int, - val condition: String, - val clouds: String, - val windSpeed: Double, - val windDirection: Double, - val rain: Double, - val snow: Double, - val night: Boolean, - val location: String, - val provider: String, - val providerUrl: String -) { - - companion object { - const val NONE = -1 - const val CLEAR = 0 - const val CLOUDY = 1 - const val COLD = 2 - const val DRIZZLE = 3 - const val HAZE = 4 - const val FOG = 5 - const val HAIL = 6 - const val HEAVY_THUNDERSTORM = 7 - const val HEAVY_THUNDERSTORM_WITH_RAIN = 8 - const val HOT = 9 - const val MOSTLY_CLOUDY = 10 - const val PARTLY_CLOUDY = 11 - const val SHOWERS = 12 - const val SLEET = 13 - const val SNOW = 14 - const val STORM = 15 - const val THUNDERSTORM = 16 - const val THUNDERSTORM_WITH_RAIN = 17 - const val WIND = 18 - const val BROKEN_CLOUDS = 19 - } -} - 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 cdbf15e2b..76cfbc8ee 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 @@ -102,8 +102,8 @@ class BreezyWeatherProvider( return when (id) { 800 -> WeatherIcon.Clear 801 -> WeatherIcon.PartlyCloudy - 803 -> WeatherIcon.Cloudy - 500 -> WeatherIcon.Showers + 803 -> WeatherIcon.Overcast + 500 -> WeatherIcon.Rain 600 -> WeatherIcon.Snow 771 -> WeatherIcon.Wind 741 -> WeatherIcon.Fog @@ -111,7 +111,7 @@ class BreezyWeatherProvider( 611 -> WeatherIcon.Sleet 511 -> WeatherIcon.Hail 210 -> WeatherIcon.Thunderstorm - 211 -> WeatherIcon.ThunderstormWithRain + 211 -> WeatherIcon.Thunderstorm else -> WeatherIcon.Unknown } } diff --git a/data/weather/src/main/java/de/mm20/launcher2/weather/brightsky/BrightSkyProvider.kt b/data/weather/src/main/java/de/mm20/launcher2/weather/brightsky/BrightSkyProvider.kt index cbb8d6010..add8c3168 100644 --- a/data/weather/src/main/java/de/mm20/launcher2/weather/brightsky/BrightSkyProvider.kt +++ b/data/weather/src/main/java/de/mm20/launcher2/weather/brightsky/BrightSkyProvider.kt @@ -87,10 +87,10 @@ internal class BrightSkyProvider( return when (icon) { "clear-day", "clear-night" -> Forecast.CLEAR "partly-cloudy-day", "partly-cloudy-night" -> Forecast.PARTLY_CLOUDY - "cloudy" -> Forecast.CLOUDY + "cloudy" -> Forecast.OVERCAST "fog" -> Forecast.FOG "wind" -> Forecast.WIND - "rain" -> Forecast.SHOWERS + "rain" -> Forecast.RAIN "sleet" -> Forecast.SLEET "snow" -> Forecast.SNOW "hail" -> Forecast.HAIL 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 05879b97b..78d8b4967 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 @@ -232,32 +232,35 @@ internal class MetNoProvider( private fun iconForCode(code: String): Int { return when (code.substringBefore("_")) { - "clearsky" -> Forecast.CLEAR - "fair" -> Forecast.PARTLY_CLOUDY - "partlycloudy" -> Forecast.MOSTLY_CLOUDY - "cloudy" -> Forecast.CLOUDY - "rainshowers", "rain", "lightrainshowers", "lightrain" -> Forecast.DRIZZLE - "rainshowersandthunder", "snowandthunder", "snowshowersandthunder", - "lightssnowshowersandthunder", "lightsleetandthunder", - "lightsnowandthunder" -> Forecast.THUNDERSTORM - - "sleetshowers", "sleet", "lightsleetshowers", "heavysleetshowers", "lightsleet", - "heavysleet" -> Forecast.SLEET - - "snowshowers", "snow", "lightsnowshowers", "heavysnowshowers", "lightsnow", - "heavysnow" -> Forecast.SNOW - - "heavyrain", "heavyrainshowers" -> Forecast.SHOWERS - "heavyrainandthunder", "sleetshowersandthunder", "rainandthunder", "sleetandthunder", - "lightrainshowersandthunder", "heavyrainshowersandthunder", - "lightssleetshowersandthunder", "lightrainandthunder" -> Forecast.THUNDERSTORM_WITH_RAIN - + "clearsky", "fair" -> Forecast.CLEAR + "partlycloudy" -> Forecast.PARTLY_CLOUDY + "cloudy" -> Forecast.OVERCAST "fog" -> Forecast.FOG - "heavysleetshowersandthunder", - "heavysleetandthunder" -> Forecast.HEAVY_THUNDERSTORM_WITH_RAIN - "heavysnowshowersandthunder", "heavysnowandthunder" -> Forecast.HEAVY_THUNDERSTORM - else -> Forecast.NONE + "lightrain", "lightrainshowers" -> Forecast.LIGHT_RAIN + "heavyrain", "heavyrainshowers" -> Forecast.HEAVY_RAIN + "rain" -> Forecast.RAIN + + "lightsleet", "lightsleetshowers", "heavysleet", "heavysleetshowers", "sleet" -> Forecast.SLEET + + "snow", "lightsnow", "lightsnowshowers", "heavysnow", "heavysnowshowers" -> Forecast.SNOW + + "rainandthunder", + "heavyrainandthunder", + "heavyrainshowersandthunder", + "lightrainshowersandthunder", + "lightrainandthunder", + "rainshowersandthunder", + "lightssnowshowersandthunder", + "lightsnowshowersandthunder", + "snowshowersandthunder", + "heavysnowandthunder", + "heavysnowshowersandthunder", + "heavysleetandthunder", + "lightssleetshowersandthunder", + "sleetshowersandthunder" -> Forecast.THUNDERSTORM + + else -> Forecast.UNKNOWN } } diff --git a/data/weather/src/main/java/de/mm20/launcher2/weather/openweathermap/OpenWeatherMapProvider.kt b/data/weather/src/main/java/de/mm20/launcher2/weather/openweathermap/OpenWeatherMapProvider.kt index b6c4bad7d..662de6c2f 100644 --- a/data/weather/src/main/java/de/mm20/launcher2/weather/openweathermap/OpenWeatherMapProvider.kt +++ b/data/weather/src/main/java/de/mm20/launcher2/weather/openweathermap/OpenWeatherMapProvider.kt @@ -162,27 +162,20 @@ internal class OpenWeatherMapProvider( private fun iconForId(id: Int): Int { return when (id) { - 200, 201, in 230..232 -> Forecast.THUNDERSTORM_WITH_RAIN - 202 -> Forecast.HEAVY_THUNDERSTORM_WITH_RAIN - 210, 211 -> Forecast.THUNDERSTORM - 212, 221 -> Forecast.HEAVY_THUNDERSTORM - in 300..302, in 310..312 -> Forecast.DRIZZLE - 313, 314, 321, in 500..504, 511, in 520..522, 531 -> Forecast.SHOWERS + 200, 201, 202, in 230..232 -> Forecast.THUNDERSTORM + 210, 211, 212, 221 -> Forecast.THUNDER + in 300..302, in 310..312, 500, 520 -> Forecast.LIGHT_RAIN + 313, 314, 321, 501, 511, 521, 531 -> Forecast.RAIN + 502, 503, 504, 522 -> Forecast.HEAVY_RAIN in 600..602 -> Forecast.SNOW 611, 612, 615, 616, in 620..622 -> Forecast.SLEET - 701, 711, 731, 741, 761, 762 -> Forecast.FOG - 721 -> Forecast.HAZE - 771, 781, in 900..902, in 958..962 -> Forecast.STORM + 701, 711, 741 -> Forecast.FOG + 721, 731, 751, 761, 762 -> Forecast.HAZE + 771, 781 -> Forecast.WIND 800 -> Forecast.CLEAR - 801 -> Forecast.PARTLY_CLOUDY - 802 -> Forecast.BROKEN_CLOUDS - 803 -> Forecast.MOSTLY_CLOUDY - 804, 951 -> Forecast.CLOUDY - 903 -> Forecast.COLD - 904 -> Forecast.HOT - 905, in 952..957 -> Forecast.WIND - 906 -> Forecast.HAIL - else -> Forecast.NONE + 801, 802, 803 -> Forecast.PARTLY_CLOUDY + 804-> Forecast.OVERCAST + else -> Forecast.UNKNOWN } } 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 3b0bc6eeb..50bac688f 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 @@ -5,14 +5,9 @@ import android.database.Cursor import android.net.Uri import android.os.CancellationSignal import android.util.Log -import androidx.core.database.getDoubleOrNull -import androidx.core.database.getIntOrNull -import androidx.core.database.getLongOrNull -import androidx.core.database.getStringOrNull import de.mm20.launcher2.crashreporter.CrashReporter import de.mm20.launcher2.plugin.PluginApi import de.mm20.launcher2.plugin.config.WeatherPluginConfig -import de.mm20.launcher2.plugin.contracts.PluginContract import de.mm20.launcher2.plugin.contracts.WeatherPluginContract import de.mm20.launcher2.plugin.contracts.WeatherPluginContract.ForecastColumns import de.mm20.launcher2.plugin.contracts.WeatherPluginContract.LocationColumns @@ -142,26 +137,35 @@ internal class PluginWeatherProvider( private fun getIcon(icon: String): Int { return when (icon) { "Clear" -> Forecast.CLEAR - "Cloudy" -> Forecast.CLOUDY - "Cold" -> Forecast.COLD - "Drizzle" -> Forecast.DRIZZLE "Haze" -> Forecast.HAZE "Fog" -> Forecast.FOG - "Hail" -> Forecast.HAIL - "HeavyThunderstorm" -> Forecast.HEAVY_THUNDERSTORM - "HeavyThunderstormWithRain" -> Forecast.HEAVY_THUNDERSTORM_WITH_RAIN - "Hot" -> Forecast.HOT - "MostlyCloudy" -> Forecast.MOSTLY_CLOUDY "PartlyCloudy" -> Forecast.PARTLY_CLOUDY - "Showers" -> Forecast.SHOWERS + "Overcast" -> Forecast.OVERCAST + "Rain" -> Forecast.RAIN + "LightRain" -> Forecast.LIGHT_RAIN + "HeavyRain" -> Forecast.HEAVY_RAIN "Sleet" -> Forecast.SLEET "Snow" -> Forecast.SNOW - "Storm" -> Forecast.STORM + "Hail" -> Forecast.HAIL + "Thunder" -> Forecast.THUNDER "Thunderstorm" -> Forecast.THUNDERSTORM - "ThunderstormWithRain" -> Forecast.THUNDERSTORM_WITH_RAIN "Wind" -> Forecast.WIND - "BrokenClouds" -> Forecast.BROKEN_CLOUDS - else -> Forecast.NONE + "ExtremeHeat" -> Forecast.EXTREME_HEAT + "ExtremeCold" -> Forecast.EXTREME_COLD + + // Deprecated values + "Cloudy" -> Forecast.OVERCAST + "Storm" -> Forecast.WIND + "HeavyThunderstorm" -> Forecast.THUNDER + "HeavyThunderstormWithRain" -> Forecast.THUNDERSTORM + "ThunderstormWithRain" -> Forecast.THUNDERSTORM + "MostlyCloudy" -> Forecast.PARTLY_CLOUDY + "BrokenClouds" -> Forecast.PARTLY_CLOUDY + "Hot" -> Forecast.EXTREME_HEAT + "Cold" -> Forecast.EXTREME_COLD + "Showers" -> Forecast.RAIN + "Drizzle" -> Forecast.LIGHT_RAIN + else -> Forecast.UNKNOWN } }