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 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.
|
||||
*/
|
||||
internal data class WeatherForecastData(
|
||||
val location: Location,
|
||||
val temperature: Float,
|
||||
val currentTime: LocalDateTime,
|
||||
val windSpeed: Float,
|
||||
val windDirection: WindDirection,
|
||||
val humidity: Int, // Percentage
|
||||
val weatherType: WeatherType
|
||||
val currentWeatherForecast: DailyForecast,
|
||||
val dailyForecasts: List<DailyForecast> = emptyList()
|
||||
) {
|
||||
companion object Companion {
|
||||
val EMPTY: WeatherForecastData = WeatherForecastData(
|
||||
Location("", ""),
|
||||
0f,
|
||||
DailyForecast(
|
||||
LocalDateTime.now(),
|
||||
0f,
|
||||
WindDirection.NORTH,
|
||||
WeatherType.CLEAR,
|
||||
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.asStateFlow
|
||||
import kotlinx.coroutines.launch
|
||||
import org.jetbrains.plugins.template.weatherApp.model.Location
|
||||
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 org.jetbrains.plugins.template.weatherApp.model.*
|
||||
import java.time.LocalDate
|
||||
import java.time.LocalDateTime
|
||||
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.
|
||||
*/
|
||||
private suspend fun getWeatherData(location: Location): WeatherForecastData {
|
||||
val temperature = (-10..40).random().toFloat()
|
||||
val windSpeed = (0..30).random().toFloat()
|
||||
val humidity = (30..90).random()
|
||||
val currentTime = LocalDateTime.of(LocalDate.now(), getRandomTime())
|
||||
|
||||
// Generate 7-day forecast data
|
||||
val dailyForecasts = generateDailyForecasts(currentTime)
|
||||
|
||||
delay(100)
|
||||
|
||||
return WeatherForecastData(
|
||||
location = location,
|
||||
temperature = temperature,
|
||||
currentTime = LocalDateTime.of(LocalDate.now(), getRandomTime()),
|
||||
windSpeed = windSpeed,
|
||||
windDirection = WindDirection.random(),
|
||||
humidity = humidity,
|
||||
weatherType = WeatherType.random()
|
||||
dailyForecasts.first(),
|
||||
dailyForecasts = dailyForecasts.drop(1)
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 {
|
||||
val hour = Random.nextInt(0, 24)
|
||||
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.background
|
||||
import androidx.compose.foundation.border
|
||||
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.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
@ -10,19 +14,22 @@ import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.graphics.Color
|
||||
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.sp
|
||||
import org.jetbrains.jewel.foundation.theme.JewelTheme
|
||||
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.Text
|
||||
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.WeatherType
|
||||
import org.jetbrains.plugins.template.weatherApp.ui.WeatherIcons
|
||||
import java.time.LocalDateTime
|
||||
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.
|
||||
@ -40,8 +47,9 @@ internal fun WeatherDetailsCard(
|
||||
weatherForecastData: WeatherForecastData,
|
||||
onReloadWeatherData: () -> Unit
|
||||
) {
|
||||
val isNightTime = isNightTime(weatherForecastData.currentTime)
|
||||
val cardColor = getCardColorByTemperature(weatherForecastData.temperature, isNightTime)
|
||||
val currentWeatherForecast = weatherForecastData.currentWeatherForecast
|
||||
val isNightTime = isNightTime(currentWeatherForecast.date)
|
||||
val cardColor = getCardColorByTemperature(currentWeatherForecast.temperature, isNightTime)
|
||||
val textColor = Color.White
|
||||
|
||||
Box(
|
||||
@ -62,7 +70,7 @@ internal fun WeatherDetailsCard(
|
||||
) {
|
||||
// Current Time
|
||||
Text(
|
||||
text = "Time: ${formatDateTime(weatherForecastData.currentTime)}",
|
||||
text = "Time: ${formatDateTime(currentWeatherForecast.date)}",
|
||||
color = textColor,
|
||||
fontSize = JewelTheme.defaultTextStyle.fontSize,
|
||||
fontWeight = FontWeight.Bold
|
||||
@ -93,9 +101,11 @@ internal fun WeatherDetailsCard(
|
||||
) {
|
||||
|
||||
Icon(
|
||||
key = WeatherIcons.cloudy,
|
||||
// key = if (isNightTime) weatherForecastData.weatherType.nightIconKey else weatherForecastData.weatherType.dayIconKey,
|
||||
contentDescription = weatherForecastData.weatherType.label,
|
||||
key = when {
|
||||
isNightTime -> currentWeatherForecast.weatherType.nightIconKey
|
||||
else -> currentWeatherForecast.weatherType.dayIconKey
|
||||
},
|
||||
contentDescription = currentWeatherForecast.weatherType.label,
|
||||
hint = EmbeddedToInlineCssSvgTransformerHint
|
||||
)
|
||||
|
||||
@ -103,7 +113,7 @@ internal fun WeatherDetailsCard(
|
||||
|
||||
// Temperature (emphasized)
|
||||
Text(
|
||||
text = "${weatherForecastData.temperature.toInt()}°C",
|
||||
text = "${currentWeatherForecast.temperature.toInt()}°C",
|
||||
color = textColor,
|
||||
fontSize = 32.sp,
|
||||
fontWeight = FontWeight.ExtraBold
|
||||
@ -129,38 +139,171 @@ internal fun WeatherDetailsCard(
|
||||
) {
|
||||
// Wind info
|
||||
Text(
|
||||
text = "Wind: ${weatherForecastData.windSpeed.toInt()} km/h ${weatherForecastData.windDirection.label}",
|
||||
text = "Wind: ${currentWeatherForecast.windSpeed.toInt()} km/h ${currentWeatherForecast.windDirection.label}",
|
||||
color = textColor,
|
||||
fontSize = 16.sp
|
||||
)
|
||||
|
||||
// Humidity info
|
||||
Text(
|
||||
text = "Humidity: ${weatherForecastData.humidity}%",
|
||||
text = "Humidity: ${currentWeatherForecast.humidity}%",
|
||||
color = textColor,
|
||||
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 {
|
||||
return when (weatherType) {
|
||||
WeatherType.CLEAR -> Color.Yellow.copy(alpha = 0.2f)
|
||||
WeatherType.CLOUDY -> Color.Gray.copy(alpha = 0.2f)
|
||||
WeatherType.PARTLY_CLOUDY -> Color.LightGray.copy(alpha = 0.2f)
|
||||
WeatherType.RAINY_AND_THUNDER,
|
||||
WeatherType.RAINY -> Color.Blue.copy(alpha = 0.2f)
|
||||
@Composable
|
||||
private fun DayForecastItem(
|
||||
forecast: DailyForecast,
|
||||
currentDate: LocalDateTime,
|
||||
textColor: Color
|
||||
) {
|
||||
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)
|
||||
WeatherType.TORNADO -> Color.DarkGray.copy(alpha = 0.2f)
|
||||
WeatherType.THUNDER -> Color.DarkGray.copy(alpha = 0.2f)
|
||||
WeatherType.FOG -> Color.LightGray.copy(alpha = 0.2f)
|
||||
WeatherType.MIST -> Color.LightGray.copy(alpha = 0.2f)
|
||||
Text(
|
||||
text = date,
|
||||
color = textColor,
|
||||
fontSize = 10.sp,
|
||||
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).
|
||||
*/
|
||||
fun isNightTime(dateTime: LocalDateTime): Boolean {
|
||||
val hour = Random.nextInt(0, 24)
|
||||
return hour < 6 || hour >= 19
|
||||
val hour = dateTime.hour
|
||||
return hour !in 6..<19
|
||||
}
|
||||
|
||||
/**
|
||||
@ -193,7 +336,17 @@ fun getCardColorByTemperature(temperature: Float, isNightTime: Boolean): Color {
|
||||
/**
|
||||
* Formats the date time to a readable string.
|
||||
*/
|
||||
fun formatDateTime(dateTime: LocalDateTime): String {
|
||||
val formatter = DateTimeFormatter.ofPattern("MMM dd, yyyy HH:mm")
|
||||
fun formatDateTime(
|
||||
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)
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user