@@ -65,7 +65,7 @@ object WeatherIconDefaults {
|
||||
@Composable
|
||||
fun themedColors(backgroundColor: Color): WeatherIconColors {
|
||||
|
||||
val themeColor = MaterialTheme.colorScheme.primary
|
||||
val themeColor = MaterialTheme.colorScheme.secondary
|
||||
val neutralColor = MaterialTheme.colorScheme.outline
|
||||
|
||||
return remember(backgroundColor, themeColor, neutralColor) {
|
||||
|
||||
@@ -3,6 +3,7 @@ package de.mm20.launcher2.ui.launcher.widgets.weather
|
||||
import android.content.ComponentName
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.res.Resources
|
||||
import android.net.Uri
|
||||
import android.text.format.DateUtils
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
@@ -21,20 +22,25 @@ 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.FlowRow
|
||||
import androidx.compose.foundation.layout.IntrinsicSize
|
||||
import androidx.compose.foundation.layout.PaddingValues
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.fillMaxHeight
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.offset
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.requiredSize
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.layout.width
|
||||
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.material3.Button
|
||||
import androidx.compose.material3.ButtonDefaults
|
||||
import androidx.compose.material3.Icon
|
||||
@@ -57,11 +63,13 @@ import androidx.compose.ui.geometry.Offset
|
||||
import androidx.compose.ui.geometry.Rect
|
||||
import androidx.compose.ui.layout.onPlaced
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.platform.LocalResources
|
||||
import androidx.compose.ui.platform.LocalView
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.semantics.contentDescription
|
||||
import androidx.compose.ui.semantics.semantics
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.unit.IntSize
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
@@ -78,7 +86,6 @@ import de.mm20.launcher2.ui.R
|
||||
import de.mm20.launcher2.ui.common.WeatherLocationSearchDialog
|
||||
import de.mm20.launcher2.ui.component.Banner
|
||||
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
|
||||
@@ -193,7 +200,7 @@ fun WeatherWidget(widget: WeatherWidget) {
|
||||
color = MaterialTheme.colorScheme.surfaceContainer.copy(alpha = MaterialTheme.transparency.surface),
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(8.dp),
|
||||
.padding(start = 8.dp, bottom = 8.dp, end = 8.dp),
|
||||
shape = MaterialTheme.shapes.small,
|
||||
) {
|
||||
Column(
|
||||
@@ -248,16 +255,13 @@ fun CurrentWeather(
|
||||
)
|
||||
}
|
||||
|
||||
val useFahrenheit = measurementSystem == MeasurementSystem.UnitedStates
|
||||
val useInches = measurementSystem == MeasurementSystem.UnitedStates
|
||||
val useMph =
|
||||
measurementSystem == MeasurementSystem.UnitedStates || measurementSystem == MeasurementSystem.UnitedKingdom
|
||||
|
||||
|
||||
var bounds by remember { mutableStateOf(Rect.Zero) }
|
||||
val view = LocalView.current
|
||||
Column(
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.height(IntrinsicSize.Max)
|
||||
.onPlaced {
|
||||
val size = it.size
|
||||
val offset = it.localToRoot(Offset.Zero)
|
||||
@@ -288,128 +292,154 @@ fun CurrentWeather(
|
||||
},
|
||||
interactionSource = remember { MutableInteractionSource() },
|
||||
indication = LocalIndication.current,
|
||||
)
|
||||
),
|
||||
horizontalArrangement = Arrangement.spacedBy(16.dp),
|
||||
) {
|
||||
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.weight(1f)
|
||||
.fillMaxHeight()
|
||||
.padding(
|
||||
top = 16.dp,
|
||||
end = 16.dp,
|
||||
start = 16.dp,
|
||||
bottom = 8.dp,
|
||||
)
|
||||
) {
|
||||
Row(
|
||||
verticalAlignment = Alignment.Top,
|
||||
horizontalArrangement = Arrangement.SpaceBetween,
|
||||
) {
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.weight(1f)
|
||||
.padding(top = 16.dp, start = 16.dp, end = 16.dp),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
Text(
|
||||
text = forecast.location,
|
||||
style = MaterialTheme.typography.titleMedium
|
||||
Text(
|
||||
text = forecast.location,
|
||||
style = MaterialTheme.typography.titleMedium
|
||||
)
|
||||
|
||||
Text(
|
||||
modifier = Modifier.padding(vertical = 2.dp),
|
||||
text = formatTemperature(
|
||||
context,
|
||||
forecast.temperature.toFloat(),
|
||||
measurementSystem
|
||||
),
|
||||
style = MaterialTheme.typography.headlineLarge,
|
||||
)
|
||||
|
||||
Text(
|
||||
text = forecast.condition,
|
||||
style = MaterialTheme.typography.bodySmall,
|
||||
color = MaterialTheme.colorScheme.secondary,
|
||||
)
|
||||
|
||||
Spacer(modifier = Modifier.weight(1f))
|
||||
|
||||
Text(
|
||||
text = "${forecast.provider} (${
|
||||
formatTime(
|
||||
LocalContext.current,
|
||||
forecast.updateTime,
|
||||
timeFormat,
|
||||
)
|
||||
}
|
||||
Tooltip(
|
||||
tooltipText = stringResource(R.string.preference_weather_provider)
|
||||
) {
|
||||
Surface(
|
||||
shape = MaterialTheme.shapes.extraSmall.copy(
|
||||
topStart = CornerSize(0),
|
||||
topEnd = CornerSize(0),
|
||||
bottomEnd = CornerSize(0)
|
||||
),
|
||||
color = MaterialTheme.colorScheme.secondaryContainer.copy(alpha = MaterialTheme.transparency.surface),
|
||||
) {
|
||||
Text(
|
||||
text = "${forecast.provider} (${
|
||||
formatTime(
|
||||
LocalContext.current,
|
||||
forecast.updateTime,
|
||||
timeFormat,
|
||||
)
|
||||
})",
|
||||
style = MaterialTheme.typography.bodySmall.copy(fontSize = 8.sp),
|
||||
modifier = Modifier
|
||||
.clickable(onClick = {
|
||||
val intent = Intent(Intent.ACTION_VIEW).apply {
|
||||
data = Uri.parse(forecast.providerUrl)
|
||||
?: return@clickable
|
||||
}
|
||||
context.tryStartActivity(intent)
|
||||
})
|
||||
.padding(start = 8.dp, top = 4.dp, bottom = 4.dp, end = 12.dp)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
Row(
|
||||
modifier = Modifier.padding(start = 16.dp),
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
Text(
|
||||
modifier = Modifier.weight(1f),
|
||||
text = formatTemperature(
|
||||
context,
|
||||
forecast.temperature.toFloat(),
|
||||
measurementSystem
|
||||
),
|
||||
style = MaterialTheme.typography.headlineMedium,
|
||||
)
|
||||
Text(
|
||||
text = forecast.condition,
|
||||
style = MaterialTheme.typography.labelMedium,
|
||||
)
|
||||
AnimatedWeatherIcon(
|
||||
modifier = Modifier.padding(
|
||||
start = 8.dp,
|
||||
end = 8.dp
|
||||
),
|
||||
icon = weatherIconById(forecast.icon),
|
||||
night = forecast.night,
|
||||
WeatherIconDefaults.colors(MaterialTheme.colorScheme.surface)
|
||||
)
|
||||
}
|
||||
})",
|
||||
style = MaterialTheme.typography.bodySmall.copy(fontSize = 8.sp),
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
modifier = Modifier
|
||||
.offset(-16.dp, 4.dp)
|
||||
.clickable(onClick = {
|
||||
val intent = Intent(Intent.ACTION_VIEW).apply {
|
||||
data = Uri.parse(forecast.providerUrl)
|
||||
?: return@clickable
|
||||
}
|
||||
context.tryStartActivity(intent)
|
||||
})
|
||||
.padding(horizontal = 16.dp, vertical = 4.dp)
|
||||
)
|
||||
|
||||
|
||||
}
|
||||
|
||||
Row(
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.padding(start = 16.dp, end = 16.dp, bottom = 12.dp, top = 8.dp)
|
||||
.fillMaxWidth(),
|
||||
horizontalArrangement = Arrangement.SpaceBetween
|
||||
.weight(1f)
|
||||
.fillMaxHeight()
|
||||
.padding(
|
||||
end = 16.dp,
|
||||
top = 16.dp,
|
||||
bottom = 16.dp,
|
||||
),
|
||||
horizontalAlignment = Alignment.End,
|
||||
) {
|
||||
if (forecast.humidity != null) {
|
||||
Tooltip(
|
||||
tooltipText = stringResource(R.string.weather_forecast_humidity)
|
||||
) {
|
||||
Row(
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
|
||||
AnimatedWeatherIcon(
|
||||
modifier = Modifier.offset(8.dp, -8.dp),
|
||||
icon = weatherIconById(forecast.icon),
|
||||
night = forecast.night,
|
||||
WeatherIconDefaults.colors(MaterialTheme.colorScheme.surface)
|
||||
)
|
||||
|
||||
CurrentWeatherDetails(
|
||||
modifier = Modifier.weight(1f),
|
||||
forecast = forecast,
|
||||
measurementSystem = measurementSystem,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun CurrentWeatherDetails(
|
||||
modifier: Modifier = Modifier,
|
||||
forecast: Forecast,
|
||||
measurementSystem: MeasurementSystem
|
||||
) {
|
||||
val context = LocalContext.current
|
||||
val resources = LocalResources.current
|
||||
|
||||
val items = remember(forecast) {
|
||||
buildList<Pair<@Composable () -> Unit, String>> {
|
||||
if (
|
||||
forecast.precipProbability != null && forecast.precipProbability!! >= 20 ||
|
||||
forecast.precipitation != null && forecast.precipitation!! >= 1
|
||||
) {
|
||||
add(
|
||||
@Composable {
|
||||
Icon(
|
||||
painter = painterResource(R.drawable.humidity_percentage_20px),
|
||||
modifier = Modifier.size(20.dp),
|
||||
painter = painterResource(R.drawable.rainy_20px),
|
||||
modifier = Modifier
|
||||
.size(20.dp),
|
||||
contentDescription = null,
|
||||
tint = MaterialTheme.colorScheme.secondary,
|
||||
contentDescription = null
|
||||
)
|
||||
Spacer(modifier = Modifier.padding(3.dp))
|
||||
Text(
|
||||
text = formatPercent(forecast.humidity!!.toFloat()),
|
||||
style = MaterialTheme.typography.bodySmall,
|
||||
} to if (forecast.precipProbability != null) {
|
||||
formatPercent(forecast.precipProbability!!.toFloat())
|
||||
} else {
|
||||
formatPrecipitation(
|
||||
context,
|
||||
forecast.precipitation?.toFloat() ?: 0f,
|
||||
measurementSystem
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
)
|
||||
}
|
||||
if (forecast.windDirection != null || forecast.windSpeed != null) {
|
||||
Tooltip(
|
||||
tooltipText = stringResource(R.string.weather_forecast_wind)
|
||||
) {
|
||||
Row(
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
if (forecast.uvIndex != null && forecast.uvIndex!! >= 3) {
|
||||
add(
|
||||
@Composable {
|
||||
Icon(
|
||||
painter = painterResource(R.drawable.sunny_20px),
|
||||
modifier = Modifier
|
||||
.size(20.dp),
|
||||
contentDescription = null,
|
||||
tint = MaterialTheme.colorScheme.secondary,
|
||||
)
|
||||
} to "UV ${forecast.uvIndex}"
|
||||
|
||||
)
|
||||
}
|
||||
if (forecast.windSpeed != null && forecast.windSpeed!! >= 3) {
|
||||
add(
|
||||
@Composable {
|
||||
if (forecast.windDirection != null) {
|
||||
// windDirection is "fromDirection"; Wind (arrow) blows into opposite direction
|
||||
val angle by animateFloatAsState(forecast.windDirection!!.toFloat() + 180f)
|
||||
Icon(
|
||||
painter = painterResource(R.drawable.north_20px),
|
||||
painter = painterResource(R.drawable.navigation_20px),
|
||||
modifier = Modifier
|
||||
.rotate(angle)
|
||||
.size(20.dp),
|
||||
@@ -424,50 +454,61 @@ fun CurrentWeather(
|
||||
tint = MaterialTheme.colorScheme.secondary,
|
||||
)
|
||||
}
|
||||
Spacer(modifier = Modifier.padding(3.dp))
|
||||
Text(
|
||||
text = if (forecast.windSpeed != null) {
|
||||
formatSpeed(
|
||||
context,
|
||||
forecast.windSpeed!!.toFloat(),
|
||||
measurementSystem
|
||||
)
|
||||
} else {
|
||||
windDirectionAsWord(forecast.windDirection!!)
|
||||
},
|
||||
style = MaterialTheme.typography.bodySmall,
|
||||
)
|
||||
}
|
||||
}
|
||||
} to formatSpeed(
|
||||
context,
|
||||
forecast.windSpeed!!.toFloat(),
|
||||
measurementSystem
|
||||
)
|
||||
|
||||
)
|
||||
}
|
||||
if (forecast.precipitation != null) {
|
||||
Tooltip(
|
||||
tooltipText = stringResource(id = R.string.weather_forecast_precipitation)
|
||||
) {
|
||||
Row(
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
if (size < 4) {
|
||||
add(
|
||||
@Composable {
|
||||
Icon(
|
||||
painter = painterResource(R.drawable.rainy_20px),
|
||||
modifier = Modifier.size(20.dp),
|
||||
painter = painterResource(R.drawable.humidity_mid_20px),
|
||||
modifier = Modifier
|
||||
.size(20.dp),
|
||||
contentDescription = null,
|
||||
tint = MaterialTheme.colorScheme.secondary,
|
||||
)
|
||||
Spacer(modifier = Modifier.padding(3.dp))
|
||||
Text(
|
||||
text = if (forecast.precipProbability != null) {
|
||||
formatPercent(forecast.precipProbability!!.toFloat())
|
||||
} else {
|
||||
formatPrecipitation(
|
||||
context,
|
||||
forecast.precipitation?.toFloat() ?: 0f,
|
||||
measurementSystem
|
||||
)
|
||||
},
|
||||
style = MaterialTheme.typography.bodySmall,
|
||||
} to formatPercent(forecast.humidity!!.toFloat()),
|
||||
)
|
||||
}
|
||||
if (size < 4 && forecast.clouds != null && forecast.clouds!! >= 30) {
|
||||
add(
|
||||
@Composable {
|
||||
Icon(
|
||||
painter = painterResource(R.drawable.cloud_20px),
|
||||
modifier = Modifier
|
||||
.size(20.dp),
|
||||
contentDescription = null,
|
||||
tint = MaterialTheme.colorScheme.secondary,
|
||||
)
|
||||
}
|
||||
}
|
||||
} to formatPercent(forecast.clouds!!.toFloat()),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
FlowRow(
|
||||
modifier = modifier,
|
||||
verticalArrangement = Arrangement.spacedBy(8.dp),
|
||||
horizontalArrangement = Arrangement.spacedBy(16.dp, Alignment.End),
|
||||
) {
|
||||
for (item in items) {
|
||||
Row(
|
||||
modifier = Modifier,
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.SpaceBetween,
|
||||
) {
|
||||
item.first()
|
||||
Text(
|
||||
textAlign = TextAlign.End,
|
||||
modifier = Modifier.padding(start = 4.dp),
|
||||
text = item.second,
|
||||
style = MaterialTheme.typography.labelSmall,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -593,7 +634,7 @@ fun WeatherDaySelector(
|
||||
val colors = WeatherIconDefaults.colors(MaterialTheme.colorScheme.surfaceBright)
|
||||
val selectedColors = WeatherIconDefaults.themedColors(
|
||||
MaterialTheme.colorScheme.secondary,
|
||||
)
|
||||
)
|
||||
|
||||
val listState = rememberLazyListState()
|
||||
LazyRow(
|
||||
@@ -693,9 +734,8 @@ private fun formatTime(context: Context, timestamp: Long, timeFormat: TimeFormat
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun windDirectionAsWord(direction: Double): String {
|
||||
return stringResource(
|
||||
private fun windDirectionAsWord(resources: Resources, direction: Double): String {
|
||||
return resources.getString(
|
||||
when (direction) {
|
||||
in 11.25..33.75 -> R.string.wind_north_north_east
|
||||
in 33.75..56.25 -> R.string.wind_north_east
|
||||
|
||||
10
core/base/src/main/res/drawable/cloud_20px.xml
Normal file
10
core/base/src/main/res/drawable/cloud_20px.xml
Normal file
@@ -0,0 +1,10 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="20dp"
|
||||
android:height="20dp"
|
||||
android:viewportWidth="960"
|
||||
android:viewportHeight="960"
|
||||
android:tint="?attr/colorControlNormal">
|
||||
<path
|
||||
android:fillColor="@android:color/white"
|
||||
android:pathData="M240,768Q160,768 104,712Q48,656 48,576Q48,500 100,444.5Q152,389 227,384Q250,299 319.5,245.5Q389,192 480,192Q583,192 659,261.5Q735,331 744,432L744,432L744,432Q814,432 863,481Q912,530 912,600Q912,670 863,719Q814,768 744,768L240,768ZM240,696L744,696Q784,696 812,668Q840,640 840,600Q840,560 812,532Q784,504 744,504L678,504L672,439Q665,365 610,314.5Q555,264 480,264Q416,264 365,302.5Q314,341 297,404L283,453L232,456Q184,459 152,493.5Q120,528 120,576Q120,626 155,661Q190,696 240,696ZM480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480L480,480L480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480L480,480L480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Z"/>
|
||||
</vector>
|
||||
10
core/base/src/main/res/drawable/humidity_mid_20px.xml
Normal file
10
core/base/src/main/res/drawable/humidity_mid_20px.xml
Normal file
@@ -0,0 +1,10 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="20dp"
|
||||
android:height="20dp"
|
||||
android:viewportWidth="960"
|
||||
android:viewportHeight="960"
|
||||
android:tint="?attr/colorControlNormal">
|
||||
<path
|
||||
android:fillColor="@android:color/white"
|
||||
android:pathData="M480,816Q360,816 276,732Q192,648 192,528Q192,474 211,425Q230,376 266,336L480,96L694,336Q730,376 749,425Q768,474 768,528Q768,648 684,732Q600,816 480,816ZM264,528L696,528Q696,488 682.5,450.5Q669,413 641,384L480,204L320,384Q292,413 278,450.5Q264,488 264,528Z"/>
|
||||
</vector>
|
||||
10
core/base/src/main/res/drawable/navigation_20px.xml
Normal file
10
core/base/src/main/res/drawable/navigation_20px.xml
Normal file
@@ -0,0 +1,10 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="20dp"
|
||||
android:height="20dp"
|
||||
android:viewportWidth="960"
|
||||
android:viewportHeight="960"
|
||||
android:tint="?attr/colorControlNormal">
|
||||
<path
|
||||
android:fillColor="@android:color/white"
|
||||
android:pathData="M480,696L257,797Q246,802 235.5,800Q225,798 217,790Q209,782 206.5,772Q204,762 209,751L447,176Q452,165 460.5,159.5Q469,154 480,154Q491,154 499.5,159.5Q508,165 513,176L751,751Q756,762 753.5,772Q751,782 743,790Q735,798 724.5,800Q714,802 703,797L480,696ZM311,694L480,617L649,694L480,284L311,694ZM480,617L480,617L480,617L480,617Z"/>
|
||||
</vector>
|
||||
10
core/base/src/main/res/drawable/sunny_20px.xml
Normal file
10
core/base/src/main/res/drawable/sunny_20px.xml
Normal file
@@ -0,0 +1,10 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="20dp"
|
||||
android:height="20dp"
|
||||
android:viewportWidth="960"
|
||||
android:viewportHeight="960"
|
||||
android:tint="?attr/colorControlNormal">
|
||||
<path
|
||||
android:fillColor="@android:color/white"
|
||||
android:pathData="M454.5,181.68Q444,171.35 444,156.09L444,84.26Q444,69 454.29,58.5Q464.58,48 479.79,48Q495,48 505.5,58.32Q516,68.65 516,83.91L516,155.74Q516,171 505.71,181.5Q495.42,192 480.21,192Q465,192 454.5,181.68ZM684,276Q673,266.14 673,251.36Q673,236.58 684,225.82L733.9,174.75Q744.59,164 759.3,164Q774,164 785,175Q796,186 796,200.5Q796,215 785,226L734,276Q723,287 709,287Q695,287 684,276ZM804.26,516Q789,516 778.5,505.71Q768,495.42 768,480.21Q768,465 778.32,454.5Q788.65,444 803.91,444L875.74,444Q891,444 901.5,454.29Q912,464.58 912,479.79Q912,495 901.68,505.5Q891.35,516 876.09,516L804.26,516ZM454.5,901.68Q444,891.35 444,876.09L444,804.26Q444,789 454.29,778.5Q464.58,768 479.79,768Q495,768 505.5,778.32Q516,788.65 516,803.91L516,875.74Q516,891 505.71,901.5Q495.42,912 480.21,912Q465,912 454.5,901.68ZM225.82,276L174.75,225.76Q164,215 164,200.5Q164,186 175,175Q186,165 200.5,164.5Q215,164 226,175L276,226Q287,237 287,251Q287,265 276,276Q265.25,287 250.91,287Q236.58,287 225.82,276ZM734,785L684,734Q673,723 673,709Q673,695 684,684Q695,673 709,673Q723,673 734,684L785.22,734.15Q796,744 796,759Q796,774 785.48,785Q774,796 759.5,796Q745,796 734,785ZM84.26,516Q69,516 58.5,505.71Q48,495.42 48,480.21Q48,465 58.32,454.5Q68.65,444 83.91,444L155.74,444Q171,444 181.5,454.29Q192,464.58 192,479.79Q192,495 181.68,505.5Q171.35,516 156.09,516L84.26,516ZM175,784.52Q164,774 164,759.5Q164,745 175,734L226,684Q237,674 251.18,674Q265.37,674 275.68,684.04Q286,695 286,709Q286,723 276,734L225.76,785.06Q215,796 200.5,796Q186,796 175,784.52ZM310,650Q240,580 240,480Q240,380 310,310Q380,240 480,240Q580,240 650,310Q720,380 720,480Q720,580 650,650Q580,720 480,720Q380,720 310,650ZM599,599Q648,550 648,480Q648,410 599,361Q550,312 480,312Q410,312 361,361Q312,410 312,480Q312,550 361,599Q410,648 480,648Q550,648 599,599ZM480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Z"/>
|
||||
</vector>
|
||||
@@ -29,6 +29,7 @@ object WeatherPluginContract {
|
||||
val Precipitation = column<Double>("precipitation")
|
||||
val RainProbability = column<Int>("rain_probability")
|
||||
val Clouds = column<Int>("clouds")
|
||||
val UvIndex = column<Double>("uv_index")
|
||||
val Location = column<String>("location")
|
||||
val Provider = column<String>("provider")
|
||||
val ProviderUrl = column<String>("provider_url")
|
||||
|
||||
@@ -44,6 +44,7 @@ import de.mm20.launcher2.database.migrations.Migration_27_28
|
||||
import de.mm20.launcher2.database.migrations.Migration_28_29
|
||||
import de.mm20.launcher2.database.migrations.Migration_29_30
|
||||
import de.mm20.launcher2.database.migrations.Migration_30_31
|
||||
import de.mm20.launcher2.database.migrations.Migration_31_32
|
||||
import de.mm20.launcher2.database.migrations.Migration_6_7
|
||||
import de.mm20.launcher2.database.migrations.Migration_7_8
|
||||
import de.mm20.launcher2.database.migrations.Migration_8_9
|
||||
@@ -67,7 +68,7 @@ import java.util.UUID
|
||||
ShapesEntity::class,
|
||||
TransparenciesEntity::class,
|
||||
TypographyEntity::class,
|
||||
], version = 31, exportSchema = true
|
||||
], version = 32, exportSchema = true
|
||||
)
|
||||
@TypeConverters(ComponentNameConverter::class)
|
||||
abstract class AppDatabase : RoomDatabase() {
|
||||
@@ -175,6 +176,7 @@ abstract class AppDatabase : RoomDatabase() {
|
||||
Migration_28_29(),
|
||||
Migration_29_30(),
|
||||
Migration_30_31(),
|
||||
Migration_31_32(),
|
||||
).build()
|
||||
if (_instance == null) _instance = instance
|
||||
return instance
|
||||
|
||||
@@ -24,7 +24,9 @@ data class ForecastEntity(
|
||||
val provider: String,
|
||||
val providerUrl: String = "",
|
||||
@ColumnInfo(name = "rainProbability") val precipProbability: Int = -1,
|
||||
@Deprecated("Deprecated")
|
||||
val snowProbability: Int = -1,
|
||||
val uvIndex: Double = -1.0,
|
||||
val updateTime: Long
|
||||
) {
|
||||
companion object {
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
package de.mm20.launcher2.database.migrations
|
||||
|
||||
import androidx.room.migration.Migration
|
||||
import androidx.sqlite.db.SupportSQLiteDatabase
|
||||
import de.mm20.launcher2.ktx.toBytes
|
||||
import de.mm20.launcher2.preferences.WidgetScreenTarget
|
||||
|
||||
class Migration_31_32 : Migration(31, 32) {
|
||||
|
||||
override fun migrate(db: SupportSQLiteDatabase) {
|
||||
db.execSQL(
|
||||
"""
|
||||
ALTER TABLE `forecasts` ADD COLUMN `uvIndex` REAL NOT NULL DEFAULT -1.0;
|
||||
""".trimIndent(),
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -45,6 +45,8 @@ data class Forecast(
|
||||
val windDirection: Double? = null,
|
||||
/** rain, in mm per hour **/
|
||||
val precipitation: Double? = null,
|
||||
/** UV index **/
|
||||
val uvIndex: Double? = null,
|
||||
/** whether this forecast is during nighttime (whether a moon icon should be used instead of sun) **/
|
||||
val night: Boolean = false,
|
||||
/** Location string **/
|
||||
@@ -79,7 +81,7 @@ data class Forecast(
|
||||
windDirection = windDirection ?: -1.0,
|
||||
windSpeed = windSpeed ?: -1.0,
|
||||
snow = -1.0,
|
||||
snowProbability = -1
|
||||
uvIndex = uvIndex ?: -1.0,
|
||||
)
|
||||
}
|
||||
|
||||
@@ -102,6 +104,7 @@ data class Forecast(
|
||||
updateTime = entity.updateTime,
|
||||
windDirection = entity.windDirection.takeIf { it >= 0.0 },
|
||||
windSpeed = entity.windSpeed.takeIf { it >= 0.0 },
|
||||
uvIndex = entity.uvIndex.takeIf { it >= 0.0 },
|
||||
)
|
||||
|
||||
companion object {
|
||||
|
||||
@@ -86,6 +86,7 @@ class BreezyWeatherProvider(
|
||||
windSpeed = hourly.windSpeed?.toDouble()?.div(3.6),
|
||||
precipProbability = hourly.precipProbability,
|
||||
windDirection = hourly.windDirection?.toDouble(),
|
||||
uvIndex = hourly.uvIndex?.toDouble(),
|
||||
updateTime = lastUpdate,
|
||||
night = isNight
|
||||
)
|
||||
|
||||
@@ -49,12 +49,12 @@ internal data class LocationForecast(
|
||||
) {
|
||||
@Serializable
|
||||
internal data class Details(
|
||||
@SerialName("air_temperature") val airTemperature: Double,
|
||||
@SerialName("cloud_area_fraction") val cloudAreaFraction: Double,
|
||||
@SerialName("relative_humidity") val relativeHumidity: Double,
|
||||
@SerialName("wind_from_direction") val windFromDirection: Double,
|
||||
@SerialName("wind_speed") val windSpeed: Double,
|
||||
@SerialName("air_pressure_at_sea_level") val airPressureAtSeaLevel: Double,
|
||||
@SerialName("air_temperature") val airTemperature: Double?,
|
||||
@SerialName("cloud_area_fraction") val cloudAreaFraction: Double?,
|
||||
@SerialName("relative_humidity") val relativeHumidity: Double?,
|
||||
@SerialName("wind_from_direction") val windFromDirection: Double?,
|
||||
@SerialName("wind_speed") val windSpeed: Double?,
|
||||
@SerialName("air_pressure_at_sea_level") val airPressureAtSeaLevel: Double?,
|
||||
)
|
||||
}
|
||||
|
||||
@@ -70,7 +70,8 @@ internal data class LocationForecast(
|
||||
|
||||
@Serializable
|
||||
internal data class Details(
|
||||
@SerialName("precipitation_amount") val precipitationAmount: Double?
|
||||
@SerialName("precipitation_amount") val precipitationAmount: Double?,
|
||||
@SerialName("ultraviolet_index_clear_sky_max") val ultravioletIndexClearSkyMax: Double?,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -97,9 +97,9 @@ internal class MetNoProvider(
|
||||
forecasts.add(
|
||||
Forecast(
|
||||
timestamp = timestamp,
|
||||
temperature = details.airTemperature + 273.15,
|
||||
temperature = (details.airTemperature ?: continue) + 273.15,
|
||||
updateTime = updatedAt,
|
||||
clouds = details.cloudAreaFraction.roundToInt(),
|
||||
clouds = details.cloudAreaFraction?.roundToInt(),
|
||||
humidity = details.relativeHumidity,
|
||||
windDirection = details.windFromDirection,
|
||||
windSpeed = details.windSpeed,
|
||||
@@ -110,6 +110,7 @@ internal class MetNoProvider(
|
||||
icon = iconForCode(symbolCode),
|
||||
condition = conditionForCode(symbolCode),
|
||||
precipitation = precipitationAmount,
|
||||
uvIndex = nextHours.details?.ultravioletIndexClearSkyMax,
|
||||
night = isNight(timestamp, lat, lon)
|
||||
|
||||
)
|
||||
@@ -239,7 +240,7 @@ internal class MetNoProvider(
|
||||
|
||||
"lightrain", "lightrainshowers" -> Forecast.LIGHT_RAIN
|
||||
"heavyrain", "heavyrainshowers" -> Forecast.HEAVY_RAIN
|
||||
"rain" -> Forecast.RAIN
|
||||
"rain", "rainshowers" -> Forecast.RAIN
|
||||
|
||||
"lightsleet", "lightsleetshowers", "heavysleet", "heavysleetshowers", "sleet" -> Forecast.SLEET
|
||||
|
||||
|
||||
@@ -68,14 +68,14 @@ data class WeatherResultClouds(
|
||||
|
||||
@Serializable
|
||||
data class CurrentWeatherResultRain(
|
||||
@SerialName("1h") val oneHour: Double,
|
||||
@SerialName("3h") val threeHours: Double,
|
||||
@SerialName("1h") val oneHour: Double?,
|
||||
@SerialName("3h") val threeHours: Double?,
|
||||
)
|
||||
|
||||
@Serializable
|
||||
data class CurrentWeatherResultSnow(
|
||||
@SerialName("1h") val oneHour: Double,
|
||||
@SerialName("3h") val threeHours: Double,
|
||||
@SerialName("1h") val oneHour: Double?,
|
||||
@SerialName("3h") val threeHours: Double?,
|
||||
)
|
||||
|
||||
@Serializable
|
||||
@@ -117,7 +117,7 @@ data class ForecastResultSnow(
|
||||
|
||||
@Serializable
|
||||
data class ForecastResultSys(
|
||||
val pod: String,
|
||||
val pod: String?,
|
||||
)
|
||||
|
||||
@Serializable
|
||||
|
||||
@@ -118,6 +118,7 @@ internal class PluginWeatherProvider(
|
||||
provider = cursor[ForecastColumns.Provider] ?: continue,
|
||||
providerUrl = cursor[ForecastColumns.ProviderUrl] ?: "",
|
||||
clouds = cursor[ForecastColumns.Clouds],
|
||||
uvIndex = cursor[ForecastColumns.UvIndex],
|
||||
humidity = cursor[ForecastColumns.Humidity]?.toDouble(),
|
||||
precipitation = cursor[ForecastColumns.Precipitation],
|
||||
precipProbability = cursor[ForecastColumns.RainProbability],
|
||||
|
||||
@@ -180,6 +180,11 @@ data class Forecast(
|
||||
* Clouds in percent
|
||||
*/
|
||||
val clouds: Int? = null,
|
||||
|
||||
/**
|
||||
* UV index (0-11)
|
||||
*/
|
||||
val uvIndex: Double? = null,
|
||||
/**
|
||||
* Location name
|
||||
*/
|
||||
|
||||
@@ -86,6 +86,7 @@ abstract class WeatherProvider(
|
||||
put(ForecastColumns.Precipitation, it.precipitation?.mm)
|
||||
put(ForecastColumns.RainProbability, it.rainProbability)
|
||||
put(ForecastColumns.Clouds, it.clouds)
|
||||
put(ForecastColumns.UvIndex, it.uvIndex)
|
||||
put(ForecastColumns.Location, it.location)
|
||||
put(ForecastColumns.Provider, it.provider)
|
||||
put(ForecastColumns.ProviderUrl, it.providerUrl)
|
||||
|
||||
Reference in New Issue
Block a user