Add moon icon to represent clear sky at night (#123)

This commit is contained in:
timklge 2025-05-05 21:26:55 +02:00 committed by GitHub
parent cff4b07d7d
commit 96bee1b55c
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
20 changed files with 113 additions and 64 deletions

View File

@ -276,7 +276,8 @@ fun lerpWeather(
windDirection = lerpAngle(start.windDirection, end.windDirection, factor),
windGusts = start.windGusts + (end.windGusts - start.windGusts) * factor,
weatherCode = closestWeatherData.weatherCode,
isForecast = closestWeatherData.isForecast
isForecast = closestWeatherData.isForecast,
isNight = closestWeatherData.isNight,
)
}

View File

@ -50,7 +50,6 @@ data class HeadwindWidgetSettings(
}
}
//Moded with openweahtermap.org
@Serializable
data class HeadwindStats(
val lastSuccessfulWeatherRequest: Long? = null,

View File

@ -28,11 +28,7 @@ import de.timklge.karooheadwind.KarooHeadwindExtension
import de.timklge.karooheadwind.R
import de.timklge.karooheadwind.TemperatureUnit
import de.timklge.karooheadwind.UpcomingRoute
import de.timklge.karooheadwind.weatherprovider.WeatherData
import de.timklge.karooheadwind.weatherprovider.WeatherDataForLocation
import de.timklge.karooheadwind.WeatherDataProvider
import de.timklge.karooheadwind.weatherprovider.WeatherDataResponse
import de.timklge.karooheadwind.weatherprovider.WeatherInterpretation
import de.timklge.karooheadwind.getHeadingFlow
import de.timklge.karooheadwind.streamCurrentForecastWeatherData
import de.timklge.karooheadwind.streamSettings
@ -40,6 +36,10 @@ import de.timklge.karooheadwind.streamUpcomingRoute
import de.timklge.karooheadwind.streamUserProfile
import de.timklge.karooheadwind.streamWidgetSettings
import de.timklge.karooheadwind.throttle
import de.timklge.karooheadwind.weatherprovider.WeatherData
import de.timklge.karooheadwind.weatherprovider.WeatherDataForLocation
import de.timklge.karooheadwind.weatherprovider.WeatherDataResponse
import de.timklge.karooheadwind.weatherprovider.WeatherInterpretation
import io.hammerhead.karooext.KarooSystemService
import io.hammerhead.karooext.extension.DataTypeImpl
import io.hammerhead.karooext.internal.ViewEmitter
@ -78,7 +78,8 @@ abstract class ForecastDataType(private val karooSystem: KarooSystemService, typ
timeLabel: String,
dateLabel: String?,
distance: Double?,
isImperial: Boolean)
isImperial: Boolean,
isNight: Boolean)
@OptIn(ExperimentalGlanceRemoteViewsApi::class)
private val glance = GlanceRemoteViews()
@ -123,7 +124,8 @@ abstract class ForecastDataType(private val karooSystem: KarooSystemService, typ
windDirection = forecastWindDirection,
windGusts = forecastWindGusts,
weatherCode = forecastWeatherCode,
isForecast = true
isForecast = true,
isNight = it < 2
)
}
@ -144,7 +146,8 @@ abstract class ForecastDataType(private val karooSystem: KarooSystemService, typ
windDirection = 180.0,
windGusts = 10.0,
weatherCode = WeatherInterpretation.getKnownWeatherCodes().random(),
isForecast = false
isForecast = false,
isNight = false
),
coords = GpsCoordinates(0.0, 0.0, distanceAlongRoute = index * distancePerHour),
timezone = "UTC",
@ -305,7 +308,8 @@ abstract class ForecastDataType(private val karooSystem: KarooSystemService, typ
timeLabel = formattedTime,
dateLabel = if (hasNewDate) formattedDate else null,
distance = null,
isImperial = settingsAndProfile.isImperial
isImperial = settingsAndProfile.isImperial,
isNight = data.current.isNight
)
previousDate = formattedDate
@ -330,7 +334,8 @@ abstract class ForecastDataType(private val karooSystem: KarooSystemService, typ
timeLabel = formattedTime,
dateLabel = if (hasNewDate) formattedDate else null,
distance = if (settingsAndProfile.settings.showDistanceInForecast) distanceFromCurrent else null,
isImperial = settingsAndProfile.isImperial
isImperial = settingsAndProfile.isImperial,
isNight = weatherData?.isNight == true
)
previousDate = formattedDate

View File

@ -34,13 +34,14 @@ fun GraphicalForecast(
distance: Double? = null,
timeLabel: String? = null,
rowAlignment: Alignment.Horizontal = Alignment.Horizontal.CenterHorizontally,
isImperial: Boolean?
isImperial: Boolean?,
isNight: Boolean,
) {
Column(modifier = GlanceModifier.fillMaxHeight().padding(1.dp).width(86.dp), horizontalAlignment = rowAlignment) {
Row(modifier = GlanceModifier.defaultWeight().wrapContentWidth(), horizontalAlignment = rowAlignment, verticalAlignment = Alignment.CenterVertically) {
Image(
modifier = GlanceModifier.defaultWeight().wrapContentWidth().padding(1.dp),
provider = ImageProvider(getWeatherIcon(current)),
provider = ImageProvider(getWeatherIcon(current, isNight)),
contentDescription = "Current weather information",
contentScale = ContentScale.Fit,
colorFilter = ColorFilter.tint(ColorProvider(Color.Black, Color.White))
@ -96,13 +97,15 @@ class GraphicalForecastDataType(karooSystem: KarooSystemService) : ForecastDataT
timeLabel: String,
dateLabel: String?,
distance: Double?,
isImperial: Boolean
isImperial: Boolean,
isNight: Boolean,
) {
GraphicalForecast(
current = current,
distance = distance,
timeLabel = timeLabel,
isImperial = isImperial
isImperial = isImperial,
isNight = isNight
)
}
}

View File

@ -25,7 +25,6 @@ import de.timklge.karooheadwind.weatherprovider.WeatherInterpretation
import io.hammerhead.karooext.KarooSystemService
import kotlin.math.absoluteValue
import kotlin.math.ceil
import kotlin.math.roundToInt
@Composable
fun PrecipitationForecast(
@ -34,7 +33,7 @@ fun PrecipitationForecast(
distance: Double? = null,
timeLabel: String? = null,
rowAlignment: Alignment.Horizontal = Alignment.Horizontal.CenterHorizontally,
isImperial: Boolean?
isImperial: Boolean?,
) {
Column(modifier = GlanceModifier.fillMaxHeight().padding(1.dp).width(86.dp), horizontalAlignment = rowAlignment) {
Row(modifier = GlanceModifier.defaultWeight().fillMaxWidth(), horizontalAlignment = rowAlignment, verticalAlignment = Alignment.CenterVertically) {
@ -95,14 +94,15 @@ class PrecipitationForecastDataType(karooSystem: KarooSystemService) : ForecastD
timeLabel: String,
dateLabel: String?,
distance: Double?,
isImperial: Boolean
isImperial: Boolean,
isNight: Boolean,
) {
PrecipitationForecast(
precipitation = ceil(precipitation).toInt(),
precipitationProbability = precipitationProbability,
distance = distance,
timeLabel = timeLabel,
isImperial = isImperial
isImperial = isImperial,
)
}
}

View File

@ -91,14 +91,15 @@ class TemperatureForecastDataType(karooSystem: KarooSystemService) : ForecastDat
timeLabel: String,
dateLabel: String?,
distance: Double?,
isImperial: Boolean
isImperial: Boolean,
isNight: Boolean
) {
TemperatureForecast(
temperature = temperature,
temperatureUnit = temperatureUnit,
distance = distance,
timeLabel = timeLabel,
isImperial = isImperial
isImperial = isImperial,
)
}
}

View File

@ -16,6 +16,7 @@ import de.timklge.karooheadwind.HeadingResponse
import de.timklge.karooheadwind.HeadwindSettings
import de.timklge.karooheadwind.KarooHeadwindExtension
import de.timklge.karooheadwind.MainActivity
import de.timklge.karooheadwind.R
import de.timklge.karooheadwind.TemperatureUnit
import de.timklge.karooheadwind.getHeadingFlow
import de.timklge.karooheadwind.streamCurrentWeatherData
@ -79,9 +80,12 @@ class WeatherDataType(
private fun previewFlow(): Flow<StreamData> = flow {
while (true){
emit(StreamData(
WeatherData(Instant.now().epochSecond, 0.0,
WeatherData(
Instant.now().epochSecond, 0.0,
20.0, 50.0, 3.0, 0.0, 1013.25, 980.0, 15.0, 30.0, 30.0,
WeatherInterpretation.getKnownWeatherCodes().random(), isForecast = false), HeadwindSettings()))
WeatherInterpretation.getKnownWeatherCodes().random(), isForecast = false,
isNight = listOf(true, false).random()
), HeadwindSettings()))
delay(5_000)
}
@ -96,7 +100,7 @@ class WeatherDataType(
val baseBitmap = BitmapFactory.decodeResource(
context.resources,
de.timklge.karooheadwind.R.drawable.arrow_0
R.drawable.arrow_0
)
val dataFlow = if (config.preview){
@ -148,7 +152,8 @@ class WeatherDataType(
},
dateLabel = formattedDate,
singleDisplay = true,
isImperial = userProfile?.preferredUnit?.distance == UserProfile.PreferredUnit.UnitType.IMPERIAL
isImperial = userProfile?.preferredUnit?.distance == UserProfile.PreferredUnit.UnitType.IMPERIAL,
isNight = data.isNight,
)
}
}

View File

@ -21,7 +21,8 @@ class WeatherForecastDataType(karooSystem: KarooSystemService) : ForecastDataTyp
timeLabel: String,
dateLabel: String?,
distance: Double?,
isImperial: Boolean
isImperial: Boolean,
isNight: Boolean,
) {
Weather(
arrowBitmap = arrowBitmap,
@ -36,7 +37,8 @@ class WeatherForecastDataType(karooSystem: KarooSystemService) : ForecastDataTyp
timeLabel = timeLabel,
dateLabel = dateLabel,
distance = distance,
isImperial = isImperial
isImperial = isImperial,
isNight = isNight,
)
}

View File

@ -45,9 +45,9 @@ fun getShortDateFormatter(): DateTimeFormatter = DateTimeFormatter.ofPattern(
}
).withZone(ZoneId.systemDefault())
fun getWeatherIcon(interpretation: WeatherInterpretation): Int {
fun getWeatherIcon(interpretation: WeatherInterpretation, isNight: Boolean): Int {
return when (interpretation){
WeatherInterpretation.CLEAR -> R.drawable.bx_clear
WeatherInterpretation.CLEAR -> if (isNight) R.drawable.moon else R.drawable.bx_clear
WeatherInterpretation.CLOUDY -> R.drawable.bx_cloud
WeatherInterpretation.RAINY -> R.drawable.bx_cloud_rain
WeatherInterpretation.SNOWY -> R.drawable.bx_cloud_snow
@ -74,7 +74,8 @@ fun Weather(
rowAlignment: Alignment.Horizontal = Alignment.Horizontal.CenterHorizontally,
dateLabel: String? = null,
singleDisplay: Boolean = false,
isImperial: Boolean?
isImperial: Boolean?,
isNight: Boolean
) {
val fontSize = if (singleDisplay) 19f else 14f
@ -83,7 +84,7 @@ fun Weather(
Row(modifier = GlanceModifier.defaultWeight().wrapContentWidth(), horizontalAlignment = rowAlignment, verticalAlignment = Alignment.CenterVertically) {
Image(
modifier = GlanceModifier.defaultWeight().wrapContentWidth().padding(1.dp),
provider = ImageProvider(getWeatherIcon(current)),
provider = ImageProvider(getWeatherIcon(current, isNight)),
contentDescription = "Current weather information",
contentScale = ContentScale.Fit,
colorFilter = ColorFilter.tint(ColorProvider(Color.Black, Color.White))

View File

@ -103,7 +103,8 @@ class WindForecastDataType(karooSystem: KarooSystemService) : ForecastDataType(k
timeLabel: String,
dateLabel: String?,
distance: Double?,
isImperial: Boolean
isImperial: Boolean,
isNight: Boolean
) {
WindForecast(
arrowBitmap = arrowBitmap,
@ -112,7 +113,7 @@ class WindForecastDataType(karooSystem: KarooSystemService) : ForecastDataType(k
gustSpeed = windGusts,
distance = distance,
timeLabel = timeLabel,
isImperial = isImperial
isImperial = isImperial,
)
}
}

View File

@ -120,6 +120,7 @@ fun WeatherScreen(onFinish: () -> Unit) {
distance = requestedWeatherPosition?.let { l -> location?.distanceTo(l)?.times(1000) },
includeDistanceLabel = false,
isImperial = profile?.preferredUnit?.distance == UserProfile.PreferredUnit.UnitType.IMPERIAL,
isNight = currentWeatherData?.isNight == true
)
}
@ -239,7 +240,8 @@ fun WeatherScreen(onFinish: () -> Unit) {
distance = distanceFromCurrent,
includeDistanceLabel = true,
precipitationProbability = weatherData?.precipitationProbability?.toInt() ?: 0,
isImperial = profile?.preferredUnit?.distance == UserProfile.PreferredUnit.UnitType.IMPERIAL
isImperial = profile?.preferredUnit?.distance == UserProfile.PreferredUnit.UnitType.IMPERIAL,
isNight = weatherData?.isNight == true,
)
}

View File

@ -46,7 +46,8 @@ fun WeatherWidget(
distance: Double? = null,
includeDistanceLabel: Boolean = false,
precipitationProbability: Int? = null,
isImperial: Boolean
isImperial: Boolean,
isNight: Boolean
) {
val fontSize = 20.sp
@ -99,7 +100,7 @@ fun WeatherWidget(
// Weather icon (larger)
Icon(
painter = painterResource(id = getWeatherIcon(current)),
painter = painterResource(id = getWeatherIcon(current, isNight)),
contentDescription = "Current weather",
modifier = Modifier.size(72.dp)
)

View File

@ -16,6 +16,7 @@ data class WeatherData(
val windDirection: Double,
val windGusts: Double,
val weatherCode: Int,
val isForecast: Boolean
val isForecast: Boolean,
val isNight: Boolean
)

View File

@ -17,6 +17,7 @@ data class OpenMeteoWeatherData(
@SerialName("wind_direction_10m") val windDirection: Double,
@SerialName("wind_gusts_10m") val windGusts: Double,
@SerialName("weather_code") val weatherCode: Int,
@SerialName("is_day") val isDay: Int,
) {
fun toWeatherData(): WeatherData = WeatherData(
temperature = temperature,
@ -31,6 +32,7 @@ data class OpenMeteoWeatherData(
weatherCode = weatherCode,
time = time,
isForecast = false,
isNight = isDay == 0
)
}

View File

@ -14,6 +14,7 @@ data class OpenMeteoWeatherForecastData(
@SerialName("wind_speed_10m") val windSpeed: List<Double>,
@SerialName("wind_direction_10m") val windDirection: List<Double>,
@SerialName("wind_gusts_10m") val windGusts: List<Double>,
@SerialName("is_day") val isDay: List<Int>,
) {
fun toWeatherData(): List<WeatherData> {
return time.mapIndexed { index, t ->
@ -25,6 +26,7 @@ data class OpenMeteoWeatherForecastData(
windDirection = windDirection[index],
windGusts = windGusts[index],
weatherCode = weatherCode[index],
isNight = isDay[index] == 0,
time = t,
isForecast = true,
)

View File

@ -34,10 +34,10 @@ class OpenMeteoWeatherProvider : WeatherProvider {
val windUnit = if (profile?.preferredUnit?.distance != UserProfile.PreferredUnit.UnitType.IMPERIAL) WindUnit.KILOMETERS_PER_HOUR else WindUnit.MILES_PER_HOUR
val response = callbackFlow {
// https://api.open-meteo.com/v1/forecast?latitude=52.52&longitude=13.41&current=surface_pressure,pressure_msl,temperature_2m,relative_humidity_2m,precipitation,weather_code,cloud_cover,wind_speed_10m,wind_direction_10m,wind_gusts_10m&hourly=temperature_2m,precipitation_probability,precipitation,weather_code,wind_speed_10m,wind_direction_10m,wind_gusts_10m&timeformat=unixtime&past_hours=1&forecast_days=1&forecast_hours=12
// https://api.open-meteo.com/v1/forecast?latitude=52.52&longitude=13.41&current=is_day,surface_pressure,pressure_msl,temperature_2m,relative_humidity_2m,precipitation,weather_code,cloud_cover,wind_speed_10m,wind_direction_10m,wind_gusts_10m&hourly=temperature_2m,precipitation_probability,precipitation,weather_code,wind_speed_10m,wind_direction_10m,wind_gusts_10m&timeformat=unixtime&past_hours=1&forecast_days=1&forecast_hours=12
val lats = gpsCoordinates.joinToString(",") { String.format(Locale.US, "%.6f", it.lat) }
val lons = gpsCoordinates.joinToString(",") { String.format(Locale.US, "%.6f", it.lon) }
val url = "https://api.open-meteo.com/v1/forecast?latitude=${lats}&longitude=${lons}&current=surface_pressure,pressure_msl,temperature_2m,relative_humidity_2m,precipitation,weather_code,cloud_cover,wind_speed_10m,wind_direction_10m,wind_gusts_10m&hourly=temperature_2m,precipitation_probability,precipitation,weather_code,wind_speed_10m,wind_direction_10m,wind_gusts_10m&timeformat=unixtime&past_hours=0&forecast_days=1&forecast_hours=12&wind_speed_unit=${windUnit.id}&precipitation_unit=${precipitationUnit.id}&temperature_unit=${temperatureUnit.id}"
val url = "https://api.open-meteo.com/v1/forecast?latitude=${lats}&longitude=${lons}&current=is_day,surface_pressure,pressure_msl,temperature_2m,relative_humidity_2m,precipitation,weather_code,cloud_cover,wind_speed_10m,wind_direction_10m,wind_gusts_10m&hourly=temperature_2m,precipitation_probability,precipitation,weather_code,wind_speed_10m,wind_direction_10m,wind_gusts_10m,is_day&timeformat=unixtime&past_hours=0&forecast_days=1&forecast_hours=12&wind_speed_unit=${windUnit.id}&precipitation_unit=${precipitationUnit.id}&temperature_unit=${temperatureUnit.id}"
Log.d(KarooHeadwindExtension.TAG, "Http request to ${url}...")

View File

@ -2,6 +2,8 @@ package de.timklge.karooheadwind.weatherprovider.openweathermap
import de.timklge.karooheadwind.weatherprovider.WeatherData
import kotlinx.serialization.Serializable
import java.time.Instant
import java.time.ZoneOffset
@Serializable
data class OpenWeatherMapForecastData(
@ -20,7 +22,16 @@ data class OpenWeatherMapForecastData(
val snow: Snow? = null,
val weather: List<Weather>
) {
fun toWeatherData(): WeatherData = WeatherData(
fun toWeatherData(currentWeatherData: OpenWeatherMapWeatherData): WeatherData {
val dtInstant = Instant.ofEpochSecond(dt)
val sunriseInstant = Instant.ofEpochSecond(currentWeatherData.sunrise)
val sunsetInstant = Instant.ofEpochSecond(currentWeatherData.sunset)
val dtTime = dtInstant.atZone(ZoneOffset.UTC).toLocalTime()
val sunriseTime = sunriseInstant.atZone(ZoneOffset.UTC).toLocalTime()
val sunsetTime = sunsetInstant.atZone(ZoneOffset.UTC).toLocalTime()
return WeatherData(
temperature = temp,
relativeHumidity = humidity.toDouble(),
precipitation = rain?.h1 ?: 0.0,
@ -34,6 +45,8 @@ data class OpenWeatherMapForecastData(
weather.firstOrNull()?.id ?: 800
),
time = dt,
isForecast = true
isForecast = true,
isNight = dtTime.isBefore(sunriseTime) || dtTime.isAfter(sunsetTime)
)
}
}

View File

@ -35,6 +35,9 @@ data class OpenWeatherMapWeatherData(
weather.firstOrNull()?.id ?: 800
),
time = dt,
isNight = let {
dt !in sunrise..<sunset
},
isForecast = false
)
}

View File

@ -14,10 +14,17 @@ data class OpenWeatherMapWeatherDataForLocation(
val current: OpenWeatherMapWeatherData,
val hourly: List<OpenWeatherMapForecastData>
){
fun toWeatherDataForLocation(distanceAlongRoute: Double?): WeatherDataForLocation = WeatherDataForLocation(
fun toWeatherDataForLocation(distanceAlongRoute: Double?): WeatherDataForLocation {
return WeatherDataForLocation(
current = current.toWeatherData(),
coords = GpsCoordinates(lat, lon, bearing = null, distanceAlongRoute = distanceAlongRoute),
coords = GpsCoordinates(
lat,
lon,
bearing = null,
distanceAlongRoute = distanceAlongRoute
),
timezone = timezone,
forecasts = hourly.map { it.toWeatherData() }
forecasts = hourly.map { it.toWeatherData(current) }
)
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.6 KiB