mirror of
https://github.com/JetBrains/intellij-platform-plugin-template.git
synced 2025-12-05 14:21:55 +00:00
Add a 7-Days forecast widget
This commit is contained in:
parent
b800d6bb17
commit
01d981038c
@ -4,27 +4,38 @@ import org.jetbrains.jewel.ui.icon.IconKey
|
|||||||
import org.jetbrains.plugins.template.weatherApp.ui.WeatherIcons
|
import org.jetbrains.plugins.template.weatherApp.ui.WeatherIcons
|
||||||
import java.time.LocalDateTime
|
import java.time.LocalDateTime
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Data class representing a daily weather forecast.
|
||||||
|
*/
|
||||||
|
internal data class DailyForecast(
|
||||||
|
val date: LocalDateTime,
|
||||||
|
val temperature: Float,
|
||||||
|
val weatherType: WeatherType,
|
||||||
|
val humidity: Int,
|
||||||
|
val windSpeed: Float,
|
||||||
|
val windDirection: WindDirection
|
||||||
|
)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Data class representing weather information to be displayed in the Weather Card.
|
* Data class representing weather information to be displayed in the Weather Card.
|
||||||
*/
|
*/
|
||||||
internal data class WeatherForecastData(
|
internal data class WeatherForecastData(
|
||||||
val location: Location,
|
val location: Location,
|
||||||
val temperature: Float,
|
val currentWeatherForecast: DailyForecast,
|
||||||
val currentTime: LocalDateTime,
|
val dailyForecasts: List<DailyForecast> = emptyList()
|
||||||
val windSpeed: Float,
|
|
||||||
val windDirection: WindDirection,
|
|
||||||
val humidity: Int, // Percentage
|
|
||||||
val weatherType: WeatherType
|
|
||||||
) {
|
) {
|
||||||
companion object Companion {
|
companion object Companion {
|
||||||
val EMPTY: WeatherForecastData = WeatherForecastData(
|
val EMPTY: WeatherForecastData = WeatherForecastData(
|
||||||
Location("", ""),
|
Location("", ""),
|
||||||
0f,
|
DailyForecast(
|
||||||
LocalDateTime.now(),
|
LocalDateTime.now(),
|
||||||
0f,
|
0f,
|
||||||
WindDirection.NORTH,
|
WeatherType.CLEAR,
|
||||||
0,
|
0,
|
||||||
WeatherType.CLEAR
|
0f,
|
||||||
|
WindDirection.NORTH,
|
||||||
|
),
|
||||||
|
emptyList()
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -8,10 +8,7 @@ import kotlinx.coroutines.flow.MutableStateFlow
|
|||||||
import kotlinx.coroutines.flow.StateFlow
|
import kotlinx.coroutines.flow.StateFlow
|
||||||
import kotlinx.coroutines.flow.asStateFlow
|
import kotlinx.coroutines.flow.asStateFlow
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import org.jetbrains.plugins.template.weatherApp.model.Location
|
import org.jetbrains.plugins.template.weatherApp.model.*
|
||||||
import org.jetbrains.plugins.template.weatherApp.model.WeatherForecastData
|
|
||||||
import org.jetbrains.plugins.template.weatherApp.model.WeatherType
|
|
||||||
import org.jetbrains.plugins.template.weatherApp.model.WindDirection
|
|
||||||
import java.time.LocalDate
|
import java.time.LocalDate
|
||||||
import java.time.LocalDateTime
|
import java.time.LocalDateTime
|
||||||
import java.time.LocalTime
|
import java.time.LocalTime
|
||||||
@ -39,23 +36,49 @@ internal class WeatherForecastService(private val cs: CoroutineScope) {
|
|||||||
* In a real application, this would fetch data from a weather API.
|
* In a real application, this would fetch data from a weather API.
|
||||||
*/
|
*/
|
||||||
private suspend fun getWeatherData(location: Location): WeatherForecastData {
|
private suspend fun getWeatherData(location: Location): WeatherForecastData {
|
||||||
val temperature = (-10..40).random().toFloat()
|
val currentTime = LocalDateTime.of(LocalDate.now(), getRandomTime())
|
||||||
val windSpeed = (0..30).random().toFloat()
|
|
||||||
val humidity = (30..90).random()
|
// Generate 7-day forecast data
|
||||||
|
val dailyForecasts = generateDailyForecasts(currentTime)
|
||||||
|
|
||||||
delay(100)
|
delay(100)
|
||||||
|
|
||||||
return WeatherForecastData(
|
return WeatherForecastData(
|
||||||
location = location,
|
location = location,
|
||||||
temperature = temperature,
|
dailyForecasts.first(),
|
||||||
currentTime = LocalDateTime.of(LocalDate.now(), getRandomTime()),
|
dailyForecasts = dailyForecasts.drop(1)
|
||||||
windSpeed = windSpeed,
|
|
||||||
windDirection = WindDirection.random(),
|
|
||||||
humidity = humidity,
|
|
||||||
weatherType = WeatherType.random()
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generates mock daily forecasts for 7 days starting from the given date.
|
||||||
|
*/
|
||||||
|
private fun generateDailyForecasts(startDate: LocalDateTime): List<DailyForecast> {
|
||||||
|
val forecasts = mutableListOf<DailyForecast>()
|
||||||
|
|
||||||
|
for (i in 0 until 8) {
|
||||||
|
val forecastDate = startDate.plusDays(i.toLong())
|
||||||
|
val temperature = (-10..40).random().toFloat()
|
||||||
|
val windSpeed = (0..30).random().toFloat()
|
||||||
|
val humidity = (30..90).random()
|
||||||
|
val weatherType = WeatherType.random()
|
||||||
|
val windDirection = WindDirection.random()
|
||||||
|
|
||||||
|
forecasts.add(
|
||||||
|
DailyForecast(
|
||||||
|
date = forecastDate,
|
||||||
|
temperature = temperature,
|
||||||
|
weatherType = weatherType,
|
||||||
|
humidity = humidity,
|
||||||
|
windSpeed = windSpeed,
|
||||||
|
windDirection = windDirection
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return forecasts
|
||||||
|
}
|
||||||
|
|
||||||
private fun getRandomTime(): LocalTime {
|
private fun getRandomTime(): LocalTime {
|
||||||
val hour = Random.nextInt(0, 24)
|
val hour = Random.nextInt(0, 24)
|
||||||
val minute = Random.nextInt(0, 60)
|
val minute = Random.nextInt(0, 60)
|
||||||
|
|||||||
@ -2,7 +2,11 @@ package org.jetbrains.plugins.template.weatherApp.ui.components
|
|||||||
|
|
||||||
import androidx.compose.foundation.ExperimentalFoundationApi
|
import androidx.compose.foundation.ExperimentalFoundationApi
|
||||||
import androidx.compose.foundation.background
|
import androidx.compose.foundation.background
|
||||||
|
import androidx.compose.foundation.border
|
||||||
import androidx.compose.foundation.layout.*
|
import androidx.compose.foundation.layout.*
|
||||||
|
import androidx.compose.foundation.lazy.LazyRow
|
||||||
|
import androidx.compose.foundation.lazy.items
|
||||||
|
import androidx.compose.foundation.lazy.rememberLazyListState
|
||||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
@ -10,19 +14,22 @@ import androidx.compose.ui.Modifier
|
|||||||
import androidx.compose.ui.draw.clip
|
import androidx.compose.ui.draw.clip
|
||||||
import androidx.compose.ui.graphics.Color
|
import androidx.compose.ui.graphics.Color
|
||||||
import androidx.compose.ui.text.font.FontWeight
|
import androidx.compose.ui.text.font.FontWeight
|
||||||
|
import androidx.compose.ui.text.style.TextAlign
|
||||||
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 org.jetbrains.jewel.foundation.theme.JewelTheme
|
import org.jetbrains.jewel.foundation.theme.JewelTheme
|
||||||
import org.jetbrains.jewel.ui.component.ActionButton
|
import org.jetbrains.jewel.ui.component.ActionButton
|
||||||
|
import org.jetbrains.jewel.ui.component.HorizontallyScrollableContainer
|
||||||
import org.jetbrains.jewel.ui.component.Icon
|
import org.jetbrains.jewel.ui.component.Icon
|
||||||
import org.jetbrains.jewel.ui.component.Text
|
import org.jetbrains.jewel.ui.component.Text
|
||||||
import org.jetbrains.jewel.ui.icons.AllIconsKeys
|
import org.jetbrains.jewel.ui.icons.AllIconsKeys
|
||||||
|
import org.jetbrains.plugins.template.weatherApp.WeatherAppColors
|
||||||
|
import org.jetbrains.plugins.template.weatherApp.model.DailyForecast
|
||||||
import org.jetbrains.plugins.template.weatherApp.model.WeatherForecastData
|
import org.jetbrains.plugins.template.weatherApp.model.WeatherForecastData
|
||||||
import org.jetbrains.plugins.template.weatherApp.model.WeatherType
|
|
||||||
import org.jetbrains.plugins.template.weatherApp.ui.WeatherIcons
|
|
||||||
import java.time.LocalDateTime
|
import java.time.LocalDateTime
|
||||||
import java.time.format.DateTimeFormatter
|
import java.time.format.DateTimeFormatter
|
||||||
import kotlin.random.Random
|
import java.time.format.TextStyle
|
||||||
|
import java.util.*
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A composable function that displays a weather card with Jewel theme.
|
* A composable function that displays a weather card with Jewel theme.
|
||||||
@ -40,8 +47,9 @@ internal fun WeatherDetailsCard(
|
|||||||
weatherForecastData: WeatherForecastData,
|
weatherForecastData: WeatherForecastData,
|
||||||
onReloadWeatherData: () -> Unit
|
onReloadWeatherData: () -> Unit
|
||||||
) {
|
) {
|
||||||
val isNightTime = isNightTime(weatherForecastData.currentTime)
|
val currentWeatherForecast = weatherForecastData.currentWeatherForecast
|
||||||
val cardColor = getCardColorByTemperature(weatherForecastData.temperature, isNightTime)
|
val isNightTime = isNightTime(currentWeatherForecast.date)
|
||||||
|
val cardColor = getCardColorByTemperature(currentWeatherForecast.temperature, isNightTime)
|
||||||
val textColor = Color.White
|
val textColor = Color.White
|
||||||
|
|
||||||
Box(
|
Box(
|
||||||
@ -62,7 +70,7 @@ internal fun WeatherDetailsCard(
|
|||||||
) {
|
) {
|
||||||
// Current Time
|
// Current Time
|
||||||
Text(
|
Text(
|
||||||
text = "Time: ${formatDateTime(weatherForecastData.currentTime)}",
|
text = "Time: ${formatDateTime(currentWeatherForecast.date)}",
|
||||||
color = textColor,
|
color = textColor,
|
||||||
fontSize = JewelTheme.defaultTextStyle.fontSize,
|
fontSize = JewelTheme.defaultTextStyle.fontSize,
|
||||||
fontWeight = FontWeight.Bold
|
fontWeight = FontWeight.Bold
|
||||||
@ -93,9 +101,11 @@ internal fun WeatherDetailsCard(
|
|||||||
) {
|
) {
|
||||||
|
|
||||||
Icon(
|
Icon(
|
||||||
key = WeatherIcons.cloudy,
|
key = when {
|
||||||
// key = if (isNightTime) weatherForecastData.weatherType.nightIconKey else weatherForecastData.weatherType.dayIconKey,
|
isNightTime -> currentWeatherForecast.weatherType.nightIconKey
|
||||||
contentDescription = weatherForecastData.weatherType.label,
|
else -> currentWeatherForecast.weatherType.dayIconKey
|
||||||
|
},
|
||||||
|
contentDescription = currentWeatherForecast.weatherType.label,
|
||||||
hint = EmbeddedToInlineCssSvgTransformerHint
|
hint = EmbeddedToInlineCssSvgTransformerHint
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -103,7 +113,7 @@ internal fun WeatherDetailsCard(
|
|||||||
|
|
||||||
// Temperature (emphasized)
|
// Temperature (emphasized)
|
||||||
Text(
|
Text(
|
||||||
text = "${weatherForecastData.temperature.toInt()}°C",
|
text = "${currentWeatherForecast.temperature.toInt()}°C",
|
||||||
color = textColor,
|
color = textColor,
|
||||||
fontSize = 32.sp,
|
fontSize = 32.sp,
|
||||||
fontWeight = FontWeight.ExtraBold
|
fontWeight = FontWeight.ExtraBold
|
||||||
@ -129,38 +139,171 @@ internal fun WeatherDetailsCard(
|
|||||||
) {
|
) {
|
||||||
// Wind info
|
// Wind info
|
||||||
Text(
|
Text(
|
||||||
text = "Wind: ${weatherForecastData.windSpeed.toInt()} km/h ${weatherForecastData.windDirection.label}",
|
text = "Wind: ${currentWeatherForecast.windSpeed.toInt()} km/h ${currentWeatherForecast.windDirection.label}",
|
||||||
color = textColor,
|
color = textColor,
|
||||||
fontSize = 16.sp
|
fontSize = 16.sp
|
||||||
)
|
)
|
||||||
|
|
||||||
// Humidity info
|
// Humidity info
|
||||||
Text(
|
Text(
|
||||||
text = "Humidity: ${weatherForecastData.humidity}%",
|
text = "Humidity: ${currentWeatherForecast.humidity}%",
|
||||||
color = textColor,
|
color = textColor,
|
||||||
fontSize = 16.sp
|
fontSize = 16.sp
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Spacer(modifier = Modifier.height(24.dp))
|
||||||
|
|
||||||
|
// 7-day forecast section
|
||||||
|
SevenDaysForecastWidget(
|
||||||
|
weatherForecastData,
|
||||||
|
Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.wrapContentHeight()
|
||||||
|
.align(Alignment.CenterHorizontally),
|
||||||
|
textColor
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun SevenDaysForecastWidget(
|
||||||
|
weatherForecastData: WeatherForecastData,
|
||||||
|
modifier: Modifier,
|
||||||
|
textColor: Color
|
||||||
|
) {
|
||||||
|
if (weatherForecastData.dailyForecasts.isNotEmpty()) {
|
||||||
|
Column(modifier) {
|
||||||
|
Text(
|
||||||
|
text = "7-Day Forecast",
|
||||||
|
color = textColor,
|
||||||
|
fontSize = 18.sp,
|
||||||
|
fontWeight = FontWeight.Bold,
|
||||||
|
modifier = Modifier.padding(bottom = 8.dp)
|
||||||
|
)
|
||||||
|
|
||||||
|
Spacer(modifier = Modifier.height(8.dp))
|
||||||
|
|
||||||
|
val scrollState = rememberLazyListState()
|
||||||
|
HorizontallyScrollableContainer(
|
||||||
|
modifier = Modifier.fillMaxWidth(),
|
||||||
|
scrollState = scrollState,
|
||||||
|
) {
|
||||||
|
LazyRow(
|
||||||
|
state = scrollState,
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth(),
|
||||||
|
horizontalArrangement = Arrangement.spacedBy(8.dp),
|
||||||
|
) {
|
||||||
|
items(weatherForecastData.dailyForecasts) { forecast ->
|
||||||
|
DayForecastItem(
|
||||||
|
forecast = forecast,
|
||||||
|
currentDate = weatherForecastData.currentWeatherForecast.date,
|
||||||
|
textColor = textColor
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns a color for the weather type indicator.
|
* A composable function that displays a single day's forecast.
|
||||||
|
*
|
||||||
|
* @param forecast The forecast data for a single day
|
||||||
|
* @param currentDate The current date for determining relative day names (Today, Tomorrow)
|
||||||
|
* @param textColor The color of the text
|
||||||
*/
|
*/
|
||||||
fun getWeatherTypeColor(weatherType: WeatherType, baseColor: Color): Color {
|
@Composable
|
||||||
return when (weatherType) {
|
private fun DayForecastItem(
|
||||||
WeatherType.CLEAR -> Color.Yellow.copy(alpha = 0.2f)
|
forecast: DailyForecast,
|
||||||
WeatherType.CLOUDY -> Color.Gray.copy(alpha = 0.2f)
|
currentDate: LocalDateTime,
|
||||||
WeatherType.PARTLY_CLOUDY -> Color.LightGray.copy(alpha = 0.2f)
|
textColor: Color
|
||||||
WeatherType.RAINY_AND_THUNDER,
|
) {
|
||||||
WeatherType.RAINY -> Color.Blue.copy(alpha = 0.2f)
|
val dayName = getDayName(forecast.date, currentDate)
|
||||||
|
val date = formatDateTime(forecast.date, showYear = false, showTime = false)
|
||||||
|
Column(
|
||||||
|
horizontalAlignment = Alignment.CenterHorizontally,
|
||||||
|
modifier = Modifier
|
||||||
|
.width(120.dp)
|
||||||
|
.border(1.dp, textColor.copy(alpha = 0.3f), RoundedCornerShape(8.dp))
|
||||||
|
.padding(8.dp)
|
||||||
|
) {
|
||||||
|
// Day name
|
||||||
|
Text(
|
||||||
|
text = dayName,
|
||||||
|
color = textColor,
|
||||||
|
fontSize = 14.sp,
|
||||||
|
fontWeight = FontWeight.Bold,
|
||||||
|
textAlign = TextAlign.Center
|
||||||
|
)
|
||||||
|
|
||||||
WeatherType.SNOWY -> Color.White.copy(alpha = 0.3f)
|
Text(
|
||||||
WeatherType.TORNADO -> Color.DarkGray.copy(alpha = 0.2f)
|
text = date,
|
||||||
WeatherType.THUNDER -> Color.DarkGray.copy(alpha = 0.2f)
|
color = textColor,
|
||||||
WeatherType.FOG -> Color.LightGray.copy(alpha = 0.2f)
|
fontSize = 10.sp,
|
||||||
WeatherType.MIST -> Color.LightGray.copy(alpha = 0.2f)
|
fontWeight = FontWeight.Bold,
|
||||||
|
textAlign = TextAlign.Center
|
||||||
|
)
|
||||||
|
|
||||||
|
Spacer(modifier = Modifier.height(8.dp))
|
||||||
|
|
||||||
|
// Weather icon
|
||||||
|
Icon(
|
||||||
|
key = if (isNightTime(forecast.date)) forecast.weatherType.nightIconKey else forecast.weatherType.dayIconKey,
|
||||||
|
contentDescription = forecast.weatherType.label,
|
||||||
|
hint = EmbeddedToInlineCssSvgTransformerHint,
|
||||||
|
modifier = Modifier.size(48.dp)
|
||||||
|
)
|
||||||
|
|
||||||
|
Spacer(modifier = Modifier.height(8.dp))
|
||||||
|
|
||||||
|
// Temperature
|
||||||
|
Text(
|
||||||
|
text = "${forecast.temperature.toInt()}°C",
|
||||||
|
color = textColor,
|
||||||
|
fontSize = 16.sp,
|
||||||
|
fontWeight = FontWeight.Bold
|
||||||
|
)
|
||||||
|
|
||||||
|
Spacer(modifier = Modifier.height(8.dp))
|
||||||
|
|
||||||
|
// Humidity
|
||||||
|
Text(
|
||||||
|
text = "Humidity: ${forecast.humidity}%",
|
||||||
|
color = textColor,
|
||||||
|
fontSize = 12.sp
|
||||||
|
)
|
||||||
|
|
||||||
|
Spacer(modifier = Modifier.height(8.dp))
|
||||||
|
|
||||||
|
// Wind direction
|
||||||
|
Text(
|
||||||
|
text = "Wind: ${forecast.windDirection.label}",
|
||||||
|
color = textColor,
|
||||||
|
fontSize = 12.sp
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the day name for a given date relative to the current date.
|
||||||
|
* Returns "Today" for the current date, "Tomorrow" for the next day,
|
||||||
|
* and the day of week plus date for other days.
|
||||||
|
*/
|
||||||
|
private fun getDayName(date: LocalDateTime, currentDate: LocalDateTime): String {
|
||||||
|
val daysDifference = date.toLocalDate().toEpochDay() - currentDate.toLocalDate().toEpochDay()
|
||||||
|
|
||||||
|
return when (daysDifference) {
|
||||||
|
0L -> "Today"
|
||||||
|
1L -> "Tomorrow"
|
||||||
|
else -> {
|
||||||
|
val dayOfWeek = date.dayOfWeek.getDisplayName(TextStyle.SHORT, Locale.getDefault())
|
||||||
|
date.dayOfMonth
|
||||||
|
"$dayOfWeek"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -169,8 +312,8 @@ fun getWeatherTypeColor(weatherType: WeatherType, baseColor: Color): Color {
|
|||||||
* Night time is considered to be between 7 PM (19:00) and 6 AM (6:00).
|
* Night time is considered to be between 7 PM (19:00) and 6 AM (6:00).
|
||||||
*/
|
*/
|
||||||
fun isNightTime(dateTime: LocalDateTime): Boolean {
|
fun isNightTime(dateTime: LocalDateTime): Boolean {
|
||||||
val hour = Random.nextInt(0, 24)
|
val hour = dateTime.hour
|
||||||
return hour < 6 || hour >= 19
|
return hour !in 6..<19
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -193,7 +336,17 @@ fun getCardColorByTemperature(temperature: Float, isNightTime: Boolean): Color {
|
|||||||
/**
|
/**
|
||||||
* Formats the date time to a readable string.
|
* Formats the date time to a readable string.
|
||||||
*/
|
*/
|
||||||
fun formatDateTime(dateTime: LocalDateTime): String {
|
fun formatDateTime(
|
||||||
val formatter = DateTimeFormatter.ofPattern("MMM dd, yyyy HH:mm")
|
dateTime: LocalDateTime,
|
||||||
|
showYear: Boolean = true,
|
||||||
|
showTime: Boolean = true
|
||||||
|
): String {
|
||||||
|
val dateFormattingPattern = buildString {
|
||||||
|
append("dd MMM")
|
||||||
|
if (showYear) append(" yyyy")
|
||||||
|
if (showTime) append(", HH:mm")
|
||||||
|
}
|
||||||
|
|
||||||
|
val formatter = DateTimeFormatter.ofPattern(dateFormattingPattern)
|
||||||
return dateTime.format(formatter)
|
return dateTime.format(formatter)
|
||||||
}
|
}
|
||||||
Loading…
Reference in New Issue
Block a user