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
import android.content.Context
import androidx.annotation.ColorInt
import androidx.annotation.ColorRes
import androidx.compose.material3.MaterialTheme
import androidx.compose.runtime.Composable
@ -10,6 +11,8 @@ 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
data class WeatherIconColors(
@ -38,31 +41,35 @@ object WeatherIconDefaults {
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, R.color.weather_sun, themeColor),
moon = harmonize(context, R.color.weather_moon, themeColor),
cloudDark1 = harmonize(context, R.color.weather_cloud_dark_1, themeColor),
cloudDark2 = harmonize(context, R.color.weather_cloud_dark_2, themeColor),
cloudMedium1 = harmonize(context, R.color.weather_cloud_medium_1, themeColor),
cloudMedium2 = harmonize(context, R.color.weather_cloud_medium_2, themeColor),
cloudLight1 = harmonize(context, R.color.weather_cloud_light_1, themeColor),
cloudLight2 = harmonize(context, R.color.weather_cloud_light_2, themeColor),
rain = harmonize(context, R.color.weather_rain, themeColor),
snow = harmonize(context, R.color.weather_snow, themeColor),
hail = harmonize(context, R.color.weather_hail, themeColor),
fog = harmonize(context, R.color.weather_fog, themeColor),
wind = harmonize(context, R.color.weather_wind, themeColor),
windDark = harmonize(context, R.color.weather_wind_dark, themeColor),
lightningBolt = harmonize(context, R.color.weather_lightning_bolt, themeColor),
hot = harmonize(context, R.color.weather_hot, themeColor),
cold = harmonize(context, R.color.weather_cold, themeColor),
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),
)
}
}
}
private fun harmonize(context: Context, @ColorRes baseColor: Int, themeColor: Color): Color {
return Color(Blend.harmonize(context.getColor(baseColor), themeColor.toArgb()))
private fun harmonize(context: Context, @ColorInt baseColor: Int, themeColor: Color): Color {
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.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.foundation.LocalIndication
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.requiredSize
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.widthIn
import androidx.compose.foundation.layout.wrapContentHeight
import androidx.compose.foundation.lazy.LazyRow
import androidx.compose.foundation.lazy.itemsIndexed
import androidx.compose.foundation.lazy.rememberLazyListState
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.foundation.shape.CornerSize
import androidx.compose.material.icons.Icons
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.material3.Button
import androidx.compose.material3.ButtonDefaults
import androidx.compose.material3.Divider
import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.OutlinedButton
@ -50,16 +57,17 @@ 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.alpha
import androidx.compose.ui.draw.rotate
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.geometry.Rect
import androidx.compose.ui.graphics.graphicsLayer
import androidx.compose.ui.layout.onPlaced
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalView
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.semantics.contentDescription
import androidx.compose.ui.semantics.semantics
import androidx.compose.ui.unit.IntSize
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
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.weather.AnimatedWeatherIcon
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.weather.DailyForecast
import de.mm20.launcher2.weather.Forecast
@ -180,11 +187,11 @@ fun WeatherWidget(widget: WeatherWidget) {
val currentDayForecasts by viewModel.currentDayForecasts
Surface(
color = MaterialTheme.colorScheme.surfaceVariant.copy(alpha = LocalTransparencyScheme.current.surface),
color = MaterialTheme.colorScheme.surfaceContainer.copy(alpha = LocalTransparencyScheme.current.surface),
modifier = Modifier.fillMaxWidth()
) {
Column(
modifier = Modifier.padding(top = 8.dp, bottom = 12.dp)
modifier = Modifier.padding(top = 12.dp, bottom = 12.dp)
) {
WeatherTimeSelector(
forecasts = currentDayForecasts,
@ -193,11 +200,7 @@ fun WeatherWidget(widget: WeatherWidget) {
onTimeSelected = {
viewModel.selectForecast(it)
},
modifier = Modifier.padding(bottom = 8.dp)
)
Divider(
modifier = Modifier.padding(horizontal = 16.dp),
color = MaterialTheme.colorScheme.onSurfaceVariant.copy(alpha = 0.12f),
modifier = Modifier.padding(bottom = 12.dp)
)
selectedDayForecast?.let {
WeatherDaySelector(
@ -207,7 +210,6 @@ fun WeatherWidget(widget: WeatherWidget) {
viewModel.selectDay(it)
},
imperialUnits = imperialUnits,
modifier = Modifier.padding(top = 8.dp)
)
}
}
@ -453,49 +455,74 @@ fun WeatherTimeSelector(
state = listState,
modifier = modifier
.fillMaxWidth(),
horizontalArrangement = Arrangement.spacedBy(12.dp),
contentPadding = PaddingValues(start = 16.dp, end = 16.dp),
horizontalArrangement = Arrangement.spacedBy(2.dp),
contentPadding = PaddingValues(start = 12.dp, end = 12.dp),
verticalAlignment = Alignment.CenterVertically
) {
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(
shape = MaterialTheme.shapes.extraSmall,
modifier = Modifier
.widthIn(min = 56.dp)
.graphicsLayer {
alpha = listState.layoutInfo.blendIntoViewScale(idx, 2f)
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
},
color = MaterialTheme.colorScheme.onSurfaceVariant.copy(backgroundAlpha)
modifier = Modifier
.widthIn(min = 60.dp),
color = MaterialTheme.colorScheme.surface,
) {
Column(
modifier = Modifier
.clickable { onTimeSelected(idx) }
.padding(4.dp),
.padding(start = 4.dp, end = 4.dp, top = 8.dp, bottom = 4.dp),
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.SpaceEvenly
) {
WeatherIcon(
modifier = Modifier
.align(Alignment.CenterHorizontally)
.semantics {
contentDescription = fc.condition
},
}
.padding(bottom = 4.dp),
icon = weatherIconById(fc.icon),
night = fc.night
)
Text(
text = dateFormat.format(fc.timestamp),
style = MaterialTheme.typography.labelSmall,
color = MaterialTheme.colorScheme.onSurfaceVariant,
softWrap = false,
modifier = Modifier.align(Alignment.CenterHorizontally)
)
Box(
contentAlignment = Alignment.Center
) {
val alpha by animateFloatAsState(if (selected) 0f else 1f)
Text(
modifier = Modifier
.alpha(alpha),
text = "${convertTemperature(imperialUnits, fc.temperature)}°",
softWrap = false,
style = MaterialTheme.typography.labelSmall,
modifier = Modifier.align(Alignment.CenterHorizontally)
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,
modifier = modifier
.fillMaxWidth(),
horizontalArrangement = Arrangement.spacedBy(16.dp),
horizontalArrangement = Arrangement.spacedBy(2.dp),
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 ->
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(
modifier = Modifier.graphicsLayer {
alpha = listState.layoutInfo.blendIntoViewScale(idx, 0.5f)
shape = when (idx) {
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.onSurfaceVariant.copy(backgroundAlpha)
color = MaterialTheme.colorScheme.surface,
) {
Row(
modifier = Modifier
.clickable { onDaySelected(idx) }
.padding(4.dp),
horizontalArrangement = Arrangement.SpaceAround,
.padding(top = 4.dp, bottom = 4.dp, start = 4.dp, end = 8.dp),
verticalAlignment = Alignment.CenterVertically
) {
WeatherIcon(icon = weatherIconById(day.icon))
Text(
text = dateFormat.format(day.timestamp),
style = MaterialTheme.typography.labelSmall,
softWrap = false,
modifier = Modifier.padding(start = 12.dp, end = 6.dp)
)
Text(
text = "${
convertTemperature(
imperialUnits,
day.minTemp
)
}° / ${convertTemperature(imperialUnits, day.maxTemp)}°",
modifier = Modifier.padding(start = 8.dp),
text = "${dateFormat.format(day.timestamp)} " +
"${convertTemperature(imperialUnits, day.minTemp)}° / " +
"${convertTemperature(imperialUnits, day.maxTemp)}°",
softWrap = false,
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)
)
}
}
}
}