Rename weather icons, add monochrome variant

This commit is contained in:
MM20
2026-02-13 11:16:17 +01:00
parent d8e95e9f64
commit de8e83aa38
13 changed files with 759 additions and 727 deletions

View File

@@ -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,
}

View File

@@ -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
)
}
}

View File

@@ -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()))
}

View File

@@ -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<IntSize>()
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
}
}

View File

@@ -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),
}

View File

@@ -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<Forecast>,
val icon: Int = getAverageIcon(hourlyForecasts)
val timestamp: Long,
val minTemp: Double,
val maxTemp: Double,
val hourlyForecasts: List<Forecast>,
val icon: Int = getAverageIcon(hourlyForecasts)
) {
companion object {
private fun getAverageIcon(forecasts: List<Forecast>): 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
}
}
}

View File

@@ -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
}
}

View File

@@ -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
}
}

View File

@@ -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
}
}

View File

@@ -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

View File

@@ -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
}
}

View File

@@ -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
}
}

View File

@@ -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
}
}