Adjust weather widget colors

This commit is contained in:
MM20 2025-06-09 21:29:45 +02:00
parent 9a9b13ae90
commit a917a00eb1
No known key found for this signature in database
GPG Key ID: 0B61A8F2DEAFA389
2 changed files with 119 additions and 69 deletions

View File

@ -1,6 +1,7 @@
package de.mm20.launcher2.ui.component.weather package de.mm20.launcher2.ui.component.weather
import android.content.Context import android.content.Context
import androidx.annotation.ColorInt
import androidx.annotation.ColorRes import androidx.annotation.ColorRes
import androidx.compose.material3.MaterialTheme import androidx.compose.material3.MaterialTheme
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
@ -10,6 +11,8 @@ import androidx.compose.ui.graphics.toArgb
import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalContext
import blend.Blend import blend.Blend
import de.mm20.launcher2.ui.R import de.mm20.launcher2.ui.R
import de.mm20.launcher2.ui.ktx.atTone
import de.mm20.launcher2.ui.locals.LocalDarkTheme
data class WeatherIconColors( data class WeatherIconColors(
@ -38,31 +41,35 @@ object WeatherIconDefaults {
val context = LocalContext.current val context = LocalContext.current
val themeColor = MaterialTheme.colorScheme.primary val themeColor = MaterialTheme.colorScheme.primary
val darkTheme = LocalDarkTheme.current
val neutral1 = MaterialTheme.colorScheme.outline
val neutral2 = MaterialTheme.colorScheme.outline
return remember(themeColor) { return remember(themeColor) {
WeatherIconColors( WeatherIconColors(
sun = harmonize(context, R.color.weather_sun, themeColor), sun = harmonize(context, 0xFFFFB300.toInt(), themeColor),
moon = harmonize(context, R.color.weather_moon, themeColor), moon = harmonize(context, 0xFF9E9E9E.toInt(), themeColor),
cloudDark1 = harmonize(context, R.color.weather_cloud_dark_1, themeColor), cloudDark1 = harmonize(context, neutral1.atTone(if (darkTheme) 40 else 30).toArgb(), themeColor),
cloudDark2 = harmonize(context, R.color.weather_cloud_dark_2, themeColor), cloudDark2 = harmonize(context, neutral1.atTone(if (darkTheme) 30 else 20).toArgb(), themeColor),
cloudMedium1 = harmonize(context, R.color.weather_cloud_medium_1, themeColor), cloudMedium1 = harmonize(context, neutral1.atTone(if (darkTheme) 60 else 50).toArgb(), themeColor),
cloudMedium2 = harmonize(context, R.color.weather_cloud_medium_2, themeColor), cloudMedium2 = harmonize(context, neutral1.atTone(if (darkTheme) 50 else 40).toArgb(), themeColor),
cloudLight1 = harmonize(context, R.color.weather_cloud_light_1, themeColor), cloudLight1 = harmonize(context, neutral1.atTone(if (darkTheme) 95 else 85).toArgb(), themeColor),
cloudLight2 = harmonize(context, R.color.weather_cloud_light_2, themeColor), cloudLight2 = harmonize(context, neutral1.atTone(if (darkTheme) 85 else 75).toArgb(), themeColor),
rain = harmonize(context, R.color.weather_rain, themeColor), rain = harmonize(context, if (darkTheme) 0xFF64B5F6.toInt() else 0xFF1E88E5.toInt(), themeColor),
snow = harmonize(context, R.color.weather_snow, themeColor), snow = harmonize(context, if (darkTheme) 0xFFF5F5F5.toInt() else 0xFFE0E0E0.toInt(), themeColor),
hail = harmonize(context, R.color.weather_hail, themeColor), hail = harmonize(context, if (darkTheme) 0xFFF5F5F5.toInt() else 0xFFE0E0E0.toInt(), themeColor),
fog = harmonize(context, R.color.weather_fog, themeColor), fog = harmonize(context, neutral1.atTone(if (darkTheme) 95 else 85).toArgb(), themeColor),
wind = harmonize(context, R.color.weather_wind, themeColor), wind = harmonize(context, neutral2.atTone(if (darkTheme) 70 else 75).toArgb(), themeColor),
windDark = harmonize(context, R.color.weather_wind_dark, themeColor), windDark = harmonize(context, neutral2.atTone(if (darkTheme) 40 else 45).toArgb(), themeColor),
lightningBolt = harmonize(context, R.color.weather_lightning_bolt, themeColor), lightningBolt = harmonize(context, 0xFFFFB300.toInt(), themeColor),
hot = harmonize(context, R.color.weather_hot, themeColor), hot = harmonize(context, if (darkTheme) 0xFFE57373.toInt() else 0xFFE53935.toInt(), themeColor),
cold = harmonize(context, R.color.weather_cold, themeColor), cold = harmonize(context, if (darkTheme) 0xFF4FC3F7.toInt() else 0xFF039BE5.toInt(), themeColor),
) )
} }
} }
} }
private fun harmonize(context: Context, @ColorRes baseColor: Int, themeColor: Color): Color { private fun harmonize(context: Context, @ColorInt baseColor: Int, themeColor: Color): Color {
return Color(Blend.harmonize(context.getColor(baseColor), themeColor.toArgb())) return Color(Blend.harmonize(baseColor, themeColor.toArgb()))
} }

View File

@ -8,22 +8,30 @@ import android.text.format.DateUtils
import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
import androidx.compose.animation.AnimatedVisibility import androidx.compose.animation.AnimatedVisibility
import androidx.compose.animation.core.animateFloatAsState 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.foundation.LocalIndication import androidx.compose.foundation.LocalIndication
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable import androidx.compose.foundation.clickable
import androidx.compose.foundation.interaction.MutableInteractionSource import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.requiredSize
import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.widthIn import androidx.compose.foundation.layout.widthIn
import androidx.compose.foundation.layout.wrapContentHeight import androidx.compose.foundation.layout.wrapContentHeight
import androidx.compose.foundation.lazy.LazyRow import androidx.compose.foundation.lazy.LazyRow
import androidx.compose.foundation.lazy.itemsIndexed import androidx.compose.foundation.lazy.itemsIndexed
import androidx.compose.foundation.lazy.rememberLazyListState import androidx.compose.foundation.lazy.rememberLazyListState
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.foundation.shape.CornerSize import androidx.compose.foundation.shape.CornerSize
import androidx.compose.material.icons.Icons import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.automirrored.rounded.OpenInNew import androidx.compose.material.icons.automirrored.rounded.OpenInNew
@ -35,7 +43,6 @@ import androidx.compose.material.icons.rounded.MyLocation
import androidx.compose.material.icons.rounded.North import androidx.compose.material.icons.rounded.North
import androidx.compose.material3.Button import androidx.compose.material3.Button
import androidx.compose.material3.ButtonDefaults import androidx.compose.material3.ButtonDefaults
import androidx.compose.material3.Divider
import androidx.compose.material3.Icon import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.OutlinedButton import androidx.compose.material3.OutlinedButton
@ -50,16 +57,17 @@ import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.alpha
import androidx.compose.ui.draw.rotate import androidx.compose.ui.draw.rotate
import androidx.compose.ui.geometry.Offset import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.geometry.Rect import androidx.compose.ui.geometry.Rect
import androidx.compose.ui.graphics.graphicsLayer
import androidx.compose.ui.layout.onPlaced import androidx.compose.ui.layout.onPlaced
import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalView import androidx.compose.ui.platform.LocalView
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.compose.ui.semantics.contentDescription import androidx.compose.ui.semantics.contentDescription
import androidx.compose.ui.semantics.semantics import androidx.compose.ui.semantics.semantics
import androidx.compose.ui.unit.IntSize
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp import androidx.compose.ui.unit.sp
import androidx.core.app.ActivityOptionsCompat import androidx.core.app.ActivityOptionsCompat
@ -78,7 +86,6 @@ import de.mm20.launcher2.ui.component.MissingPermissionBanner
import de.mm20.launcher2.ui.component.Tooltip import de.mm20.launcher2.ui.component.Tooltip
import de.mm20.launcher2.ui.component.weather.AnimatedWeatherIcon import de.mm20.launcher2.ui.component.weather.AnimatedWeatherIcon
import de.mm20.launcher2.ui.component.weather.WeatherIcon import de.mm20.launcher2.ui.component.weather.WeatherIcon
import de.mm20.launcher2.ui.ktx.blendIntoViewScale
import de.mm20.launcher2.ui.theme.transparency.LocalTransparencyScheme import de.mm20.launcher2.ui.theme.transparency.LocalTransparencyScheme
import de.mm20.launcher2.weather.DailyForecast import de.mm20.launcher2.weather.DailyForecast
import de.mm20.launcher2.weather.Forecast import de.mm20.launcher2.weather.Forecast
@ -180,11 +187,11 @@ fun WeatherWidget(widget: WeatherWidget) {
val currentDayForecasts by viewModel.currentDayForecasts val currentDayForecasts by viewModel.currentDayForecasts
Surface( Surface(
color = MaterialTheme.colorScheme.surfaceVariant.copy(alpha = LocalTransparencyScheme.current.surface), color = MaterialTheme.colorScheme.surfaceContainer.copy(alpha = LocalTransparencyScheme.current.surface),
modifier = Modifier.fillMaxWidth() modifier = Modifier.fillMaxWidth()
) { ) {
Column( Column(
modifier = Modifier.padding(top = 8.dp, bottom = 12.dp) modifier = Modifier.padding(top = 12.dp, bottom = 12.dp)
) { ) {
WeatherTimeSelector( WeatherTimeSelector(
forecasts = currentDayForecasts, forecasts = currentDayForecasts,
@ -193,11 +200,7 @@ fun WeatherWidget(widget: WeatherWidget) {
onTimeSelected = { onTimeSelected = {
viewModel.selectForecast(it) viewModel.selectForecast(it)
}, },
modifier = Modifier.padding(bottom = 8.dp) modifier = Modifier.padding(bottom = 12.dp)
)
Divider(
modifier = Modifier.padding(horizontal = 16.dp),
color = MaterialTheme.colorScheme.onSurfaceVariant.copy(alpha = 0.12f),
) )
selectedDayForecast?.let { selectedDayForecast?.let {
WeatherDaySelector( WeatherDaySelector(
@ -207,7 +210,6 @@ fun WeatherWidget(widget: WeatherWidget) {
viewModel.selectDay(it) viewModel.selectDay(it)
}, },
imperialUnits = imperialUnits, imperialUnits = imperialUnits,
modifier = Modifier.padding(top = 8.dp)
) )
} }
} }
@ -453,49 +455,74 @@ fun WeatherTimeSelector(
state = listState, state = listState,
modifier = modifier modifier = modifier
.fillMaxWidth(), .fillMaxWidth(),
horizontalArrangement = Arrangement.spacedBy(12.dp), horizontalArrangement = Arrangement.spacedBy(2.dp),
contentPadding = PaddingValues(start = 16.dp, end = 16.dp), contentPadding = PaddingValues(start = 12.dp, end = 12.dp),
verticalAlignment = Alignment.CenterVertically verticalAlignment = Alignment.CenterVertically
) { ) {
itemsIndexed(forecasts, key = { idx, _ -> idx }) { idx, fc -> itemsIndexed(forecasts, key = { idx, _ -> idx }) { idx, fc ->
val backgroundAlpha = if (fc == selectedForecast) 0.2f else 0.0f val selected = fc == selectedForecast
val sm = MaterialTheme.shapes.small
val xs = MaterialTheme.shapes.extraSmall
Surface( Surface(
shape = MaterialTheme.shapes.extraSmall, shape = when (idx) {
0 -> xs.copy(
topStart = sm.topStart,
bottomStart = sm.bottomStart
)
forecasts.lastIndex -> xs.copy(
topEnd = sm.topEnd,
bottomEnd = sm.bottomEnd
)
else -> MaterialTheme.shapes.extraSmall
},
modifier = Modifier modifier = Modifier
.widthIn(min = 56.dp) .widthIn(min = 60.dp),
.graphicsLayer { color = MaterialTheme.colorScheme.surface,
alpha = listState.layoutInfo.blendIntoViewScale(idx, 2f)
},
color = MaterialTheme.colorScheme.onSurfaceVariant.copy(backgroundAlpha)
) { ) {
Column( Column(
modifier = Modifier modifier = Modifier
.clickable { onTimeSelected(idx) } .clickable { onTimeSelected(idx) }
.padding(4.dp), .padding(start = 4.dp, end = 4.dp, top = 8.dp, bottom = 4.dp),
horizontalAlignment = Alignment.CenterHorizontally, horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.SpaceEvenly
) { ) {
WeatherIcon( WeatherIcon(
modifier = Modifier modifier = Modifier
.align(Alignment.CenterHorizontally) .align(Alignment.CenterHorizontally)
.semantics { .semantics {
contentDescription = fc.condition contentDescription = fc.condition
}, }
.padding(bottom = 4.dp),
icon = weatherIconById(fc.icon), icon = weatherIconById(fc.icon),
night = fc.night night = fc.night
) )
Text( Text(
text = dateFormat.format(fc.timestamp), text = dateFormat.format(fc.timestamp),
style = MaterialTheme.typography.labelSmall, style = MaterialTheme.typography.labelSmall,
color = MaterialTheme.colorScheme.onSurfaceVariant,
softWrap = false, softWrap = false,
modifier = Modifier.align(Alignment.CenterHorizontally) modifier = Modifier.align(Alignment.CenterHorizontally)
) )
Text( Box(
text = "${convertTemperature(imperialUnits, fc.temperature)}°", contentAlignment = Alignment.Center
softWrap = false, ) {
style = MaterialTheme.typography.labelSmall, val alpha by animateFloatAsState(if (selected) 0f else 1f)
modifier = Modifier.align(Alignment.CenterHorizontally) Text(
) modifier = Modifier
.alpha(alpha),
text = "${convertTemperature(imperialUnits, fc.temperature)}°",
softWrap = false,
style = MaterialTheme.typography.labelSmall,
color = MaterialTheme.colorScheme.onSurfaceVariant,
)
Box(
modifier = Modifier
.alpha(1f - alpha)
.requiredSize(8.dp)
.background(MaterialTheme.colorScheme.primary, CircleShape)
)
}
} }
} }
} }
@ -517,44 +544,60 @@ fun WeatherDaySelector(
state = listState, state = listState,
modifier = modifier modifier = modifier
.fillMaxWidth(), .fillMaxWidth(),
horizontalArrangement = Arrangement.spacedBy(16.dp), horizontalArrangement = Arrangement.spacedBy(2.dp),
verticalAlignment = Alignment.CenterVertically, verticalAlignment = Alignment.CenterVertically,
contentPadding = PaddingValues(start = 16.dp, end = 16.dp), contentPadding = PaddingValues(start = 12.dp, end = 12.dp),
) { ) {
itemsIndexed(days, key = { idx, _ -> idx }) { idx, day -> itemsIndexed(days, key = { idx, _ -> idx }) { idx, day ->
val backgroundAlpha = if (day == selectedDay) 0.2f else 0.0f val selected = day == selectedDay
val sm = MaterialTheme.shapes.small
val xs = MaterialTheme.shapes.extraSmall
Surface( Surface(
modifier = Modifier.graphicsLayer { shape = when (idx) {
alpha = listState.layoutInfo.blendIntoViewScale(idx, 0.5f) 0 -> xs.copy(
topStart = sm.topStart,
bottomStart = sm.bottomStart
)
days.lastIndex -> xs.copy(
topEnd = sm.topEnd,
bottomEnd = sm.bottomEnd
)
else -> MaterialTheme.shapes.extraSmall
}, },
shape = MaterialTheme.shapes.extraSmall, color = MaterialTheme.colorScheme.surface,
color = MaterialTheme.colorScheme.onSurfaceVariant.copy(backgroundAlpha)
) { ) {
Row( Row(
modifier = Modifier modifier = Modifier
.clickable { onDaySelected(idx) } .clickable { onDaySelected(idx) }
.padding(4.dp), .padding(top = 4.dp, bottom = 4.dp, start = 4.dp, end = 8.dp),
horizontalArrangement = Arrangement.SpaceAround,
verticalAlignment = Alignment.CenterVertically verticalAlignment = Alignment.CenterVertically
) { ) {
WeatherIcon(icon = weatherIconById(day.icon)) WeatherIcon(icon = weatherIconById(day.icon))
Text( Text(
text = dateFormat.format(day.timestamp), modifier = Modifier.padding(start = 8.dp),
style = MaterialTheme.typography.labelSmall, text = "${dateFormat.format(day.timestamp)} " +
softWrap = false, "${convertTemperature(imperialUnits, day.minTemp)}° / " +
modifier = Modifier.padding(start = 12.dp, end = 6.dp) "${convertTemperature(imperialUnits, day.maxTemp)}°",
)
Text(
text = "${
convertTemperature(
imperialUnits,
day.minTemp
)
}° / ${convertTemperature(imperialUnits, day.maxTemp)}°",
softWrap = false, softWrap = false,
style = MaterialTheme.typography.labelSmall, style = MaterialTheme.typography.labelSmall,
color = MaterialTheme.colorScheme.onSurfaceVariant,
) )
val spec = MaterialTheme.motionScheme.fastSpatialSpec<IntSize>()
AnimatedVisibility(
selected,
enter = fadeIn() + expandHorizontally(spec),
exit = fadeOut() + shrinkHorizontally(spec),
) {
Box(
modifier = Modifier
.padding(start = 8.dp)
.size(8.dp)
.background(MaterialTheme.colorScheme.primary, CircleShape)
)
}
} }
} }
} }