added 'days until' and UI improvements
This commit is contained in:
@@ -42,6 +42,12 @@ struct WeatherState {
|
|||||||
tomorrow_summary: String,
|
tomorrow_summary: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
struct Countdown {
|
||||||
|
label: String,
|
||||||
|
month: u32,
|
||||||
|
day: u32,
|
||||||
|
}
|
||||||
|
|
||||||
fn get_config_dir() -> PathBuf {
|
fn get_config_dir() -> PathBuf {
|
||||||
if let Ok(home) = env::var("HOME") {
|
if let Ok(home) = env::var("HOME") {
|
||||||
PathBuf::from(home).join(".config/hypr/scripts/statmon")
|
PathBuf::from(home).join(".config/hypr/scripts/statmon")
|
||||||
@@ -112,8 +118,9 @@ fn fetch_weather_and_forecast(api_key: &str) -> (String, String) {
|
|||||||
if let Some(list) = json["list"].as_array() {
|
if let Some(list) = json["list"].as_array() {
|
||||||
let mut max_pop_today = 0.0f64;
|
let mut max_pop_today = 0.0f64;
|
||||||
let mut max_pop_tomorrow = 0.0f64;
|
let mut max_pop_tomorrow = 0.0f64;
|
||||||
let mut today_rain = false;
|
|
||||||
let mut tomorrow_rain = false;
|
let mut max_rain_id_today: Option<u64> = None;
|
||||||
|
let mut max_rain_id_tomorrow: Option<u64> = None;
|
||||||
|
|
||||||
let now = Local::now().date_naive();
|
let now = Local::now().date_naive();
|
||||||
let tomorrow = now + chrono::Days::new(1);
|
let tomorrow = now + chrono::Days::new(1);
|
||||||
@@ -122,31 +129,53 @@ fn fetch_weather_and_forecast(api_key: &str) -> (String, String) {
|
|||||||
if let Some(dt_txt) = item["dt_txt"].as_str() {
|
if let Some(dt_txt) = item["dt_txt"].as_str() {
|
||||||
if let Ok(date) = NaiveDate::parse_from_str(&dt_txt[..10], "%Y-%m-%d") {
|
if let Ok(date) = NaiveDate::parse_from_str(&dt_txt[..10], "%Y-%m-%d") {
|
||||||
let pop = item["pop"].as_f64().unwrap_or(0.0);
|
let pop = item["pop"].as_f64().unwrap_or(0.0);
|
||||||
let weather_main = item["weather"][0]["main"].as_str().unwrap_or("");
|
|
||||||
|
// Extract rain condition IDs in the 3xx (drizzle) and 5xx (rain) ranges
|
||||||
|
let weather_ids: Vec<u64> = item["weather"]
|
||||||
|
.as_array()
|
||||||
|
.map(|arr| {
|
||||||
|
arr.iter()
|
||||||
|
.filter_map(|w| w["id"].as_u64())
|
||||||
|
.filter(|&id| (300..600).contains(&id))
|
||||||
|
.collect()
|
||||||
|
})
|
||||||
|
.unwrap_or_default();
|
||||||
|
|
||||||
|
let max_block_id = weather_ids.into_iter().max();
|
||||||
|
|
||||||
if date == now {
|
if date == now {
|
||||||
if pop > max_pop_today { max_pop_today = pop; }
|
if pop > max_pop_today { max_pop_today = pop; }
|
||||||
if weather_main.to_lowercase().contains("rain") || pop > 0.3 {
|
if let Some(id) = max_block_id {
|
||||||
today_rain = true;
|
max_rain_id_today = Some(max_rain_id_today.map_or(id, |m| m.max(id)));
|
||||||
}
|
}
|
||||||
} else if date == tomorrow {
|
} else if date == tomorrow {
|
||||||
if pop > max_pop_tomorrow { max_pop_tomorrow = pop; }
|
if pop > max_pop_tomorrow { max_pop_tomorrow = pop; }
|
||||||
if weather_main.to_lowercase().contains("rain") || pop > 0.3 {
|
if let Some(id) = max_block_id {
|
||||||
tomorrow_rain = true;
|
max_rain_id_tomorrow = Some(max_rain_id_tomorrow.map_or(id, |m| m.max(id)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Helper closure to map max weather ID to human-readable status
|
||||||
|
let classify_rain = |max_id: Option<u64>| -> &'static str {
|
||||||
|
match max_id {
|
||||||
|
Some(502..=504 | 522 | 531) => "Heavy Rain",
|
||||||
|
Some(501 | 521) => "Moderate Rain",
|
||||||
|
Some(500 | 520 | 300..=321) => "Light Rain",
|
||||||
|
_ => "Clear/No Major Rain",
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
today_str = format!(
|
today_str = format!(
|
||||||
"{} (Rain Chance: {:.0}%)",
|
"{} (Rain Chance: {:.0}%)",
|
||||||
if today_rain { "Rain Expected" } else { "Clear/No Major Rain" },
|
classify_rain(max_rain_id_today),
|
||||||
max_pop_today * 100.0
|
max_pop_today * 100.0
|
||||||
);
|
);
|
||||||
tomorrow_str = format!(
|
tomorrow_str = format!(
|
||||||
"{} (Rain Chance: {:.0}%)",
|
"{} (Rain Chance: {:.0}%)",
|
||||||
if tomorrow_rain { "Rain Expected" } else { "Clear/No Major Rain" },
|
classify_rain(max_rain_id_tomorrow),
|
||||||
max_pop_tomorrow * 100.0
|
max_pop_tomorrow * 100.0
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -181,9 +210,46 @@ fn get_upcoming_birthdays() -> Vec<String> {
|
|||||||
upcoming
|
upcoming
|
||||||
}
|
}
|
||||||
|
|
||||||
fn render_calendar_and_birthdays(
|
fn get_countdowns() -> Vec<String> {
|
||||||
|
let today = Local::now().date_naive();
|
||||||
|
let current_year = today.year();
|
||||||
|
|
||||||
|
let targets = vec![
|
||||||
|
Countdown { label: "Avy's Birthday".to_string(), month: 4, day: 6 },
|
||||||
|
Countdown { label: "4th of July".to_string(), month: 7, day: 4 },
|
||||||
|
Countdown { label: "Cal's Birthday".to_string(), month: 9, day: 24 },
|
||||||
|
Countdown { label: "Halloween".to_string(), month: 10, day: 31 },
|
||||||
|
Countdown { label: "Christmas".to_string(), month: 12, day: 25 },
|
||||||
|
];
|
||||||
|
|
||||||
|
let mut results = Vec::new();
|
||||||
|
|
||||||
|
for t in targets {
|
||||||
|
let target_date = if let Some(d) = NaiveDate::from_ymd_opt(current_year, t.month, t.day) {
|
||||||
|
if d >= today {
|
||||||
|
d
|
||||||
|
} else {
|
||||||
|
NaiveDate::from_ymd_opt(current_year + 1, t.month, t.day).unwrap()
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
|
||||||
|
let diff = target_date.signed_duration_since(today).num_days();
|
||||||
|
if diff == 0 {
|
||||||
|
results.push(format!("{:<14}: TODAY!", t.label));
|
||||||
|
} else {
|
||||||
|
results.push(format!("{:<14}: {} days", t.label, diff));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
results
|
||||||
|
}
|
||||||
|
|
||||||
|
fn render_bottom_section(
|
||||||
stdout: &mut std::io::Stdout,
|
stdout: &mut std::io::Stdout,
|
||||||
upcoming_birthdays: &[String],
|
upcoming_birthdays: &[String],
|
||||||
|
countdowns: &[String],
|
||||||
) -> Result<(), Box<dyn std::error::Error>> {
|
) -> Result<(), Box<dyn std::error::Error>> {
|
||||||
let now = Local::now();
|
let now = Local::now();
|
||||||
let year = now.year();
|
let year = now.year();
|
||||||
@@ -202,14 +268,15 @@ fn render_calendar_and_birthdays(
|
|||||||
|
|
||||||
let start_weekday = first_of_month.weekday().num_days_from_sunday();
|
let start_weekday = first_of_month.weekday().num_days_from_sunday();
|
||||||
|
|
||||||
// Side-by-Side Headers
|
// 3-Column Headers
|
||||||
execute!(stdout, SetForegroundColor(ACCENT))?;
|
execute!(stdout, SetForegroundColor(ACCENT))?;
|
||||||
write!(stdout, " {:<27}", format!("CALENDAR ({} {})", now.format("%B"), year))?;
|
write!(stdout, " {:<27}", format!("CALENDAR ({} {})", now.format("%B"), year))?;
|
||||||
execute!(stdout, SetForegroundColor(ACCENT))?;
|
write!(stdout, "{:<34}", "UPCOMING EVENTS")?;
|
||||||
writeln!(stdout, "UPCOMING EVENTS")?;
|
writeln!(stdout, "DAYS UNTIL")?;
|
||||||
|
|
||||||
execute!(stdout, SetForegroundColor(FG_MAIN))?;
|
execute!(stdout, SetForegroundColor(FG_MAIN))?;
|
||||||
write!(stdout, " Su Mo Tu We Th Fr Sa ")?;
|
write!(stdout, " Su Mo Tu We Th Fr Sa ")?;
|
||||||
|
write!(stdout, "--------------------------------- ")?;
|
||||||
writeln!(stdout, "----------------------------------")?;
|
writeln!(stdout, "----------------------------------")?;
|
||||||
|
|
||||||
// Build Calendar Lines
|
// Build Calendar Lines
|
||||||
@@ -239,9 +306,13 @@ fn render_calendar_and_birthdays(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let total_rows = calendar_lines.len().max(upcoming_birthdays.len().max(1));
|
let total_rows = calendar_lines
|
||||||
|
.len()
|
||||||
|
.max(upcoming_birthdays.len().max(1))
|
||||||
|
.max(countdowns.len());
|
||||||
|
|
||||||
for i in 0..total_rows {
|
for i in 0..total_rows {
|
||||||
|
// Col 1: Calendar
|
||||||
execute!(stdout, SetForegroundColor(FG_MAIN))?;
|
execute!(stdout, SetForegroundColor(FG_MAIN))?;
|
||||||
if i < calendar_lines.len() {
|
if i < calendar_lines.len() {
|
||||||
write!(stdout, " {} ", calendar_lines[i])?;
|
write!(stdout, " {} ", calendar_lines[i])?;
|
||||||
@@ -251,12 +322,23 @@ fn render_calendar_and_birthdays(
|
|||||||
|
|
||||||
write!(stdout, " ")?;
|
write!(stdout, " ")?;
|
||||||
|
|
||||||
|
// Col 2: Upcoming Events
|
||||||
if i < upcoming_birthdays.len() {
|
if i < upcoming_birthdays.len() {
|
||||||
execute!(stdout, SetForegroundColor(ACCENT))?;
|
execute!(stdout, SetForegroundColor(ACCENT))?;
|
||||||
writeln!(stdout, "• {}", upcoming_birthdays[i])?;
|
write!(stdout, "{:<32}", format!("• {}", upcoming_birthdays[i]))?;
|
||||||
} else if i == 0 && upcoming_birthdays.is_empty() {
|
} else if i == 0 && upcoming_birthdays.is_empty() {
|
||||||
execute!(stdout, SetForegroundColor(FG_MAIN))?;
|
execute!(stdout, SetForegroundColor(FG_MAIN))?;
|
||||||
writeln!(stdout, "No events in next 2 days")?;
|
write!(stdout, "{:<32}", "No events in next 2 days")?;
|
||||||
|
} else {
|
||||||
|
write!(stdout, " ")?;
|
||||||
|
}
|
||||||
|
|
||||||
|
write!(stdout, " ")?;
|
||||||
|
|
||||||
|
// Col 3: Days Until
|
||||||
|
if i < countdowns.len() {
|
||||||
|
execute!(stdout, SetForegroundColor(ACCENT))?;
|
||||||
|
writeln!(stdout, "• {}", countdowns[i])?;
|
||||||
} else {
|
} else {
|
||||||
writeln!(stdout)?;
|
writeln!(stdout)?;
|
||||||
}
|
}
|
||||||
@@ -422,7 +504,6 @@ fn print_line(stdout: &mut std::io::Stdout, label: &str, value: &str, val_color:
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn main() -> Result<(), Box<dyn std::error::Error>> {
|
fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||||
// Load .env from ~/.config/hypr/scripts/statmon/.env
|
|
||||||
let env_path = get_config_dir().join(".env");
|
let env_path = get_config_dir().join(".env");
|
||||||
let _ = dotenvy::from_path(&env_path);
|
let _ = dotenvy::from_path(&env_path);
|
||||||
|
|
||||||
@@ -453,8 +534,6 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
|
|||||||
tomorrow_summary: "Fetching...".to_string(),
|
tomorrow_summary: "Fetching...".to_string(),
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut upcoming_birthdays: Vec<String>;
|
|
||||||
|
|
||||||
thread::sleep(Duration::from_millis(1000));
|
thread::sleep(Duration::from_millis(1000));
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
@@ -467,7 +546,7 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
|
|||||||
weather.aqi_desc = aqi_desc;
|
weather.aqi_desc = aqi_desc;
|
||||||
weather.aqi_val = aqi_val;
|
weather.aqi_val = aqi_val;
|
||||||
|
|
||||||
if aqi_val >= 5 {
|
if aqi_val >= 4 {
|
||||||
send_notification("Air Quality Warning", &format!("Air quality in 95662 is concerning: {}", weather.aqi_desc), "critical");
|
send_notification("Air Quality Warning", &format!("Air quality in 95662 is concerning: {}", weather.aqi_desc), "critical");
|
||||||
}
|
}
|
||||||
last_aqi_fetch = Instant::now();
|
last_aqi_fetch = Instant::now();
|
||||||
@@ -481,8 +560,8 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
|
|||||||
last_weather_fetch = Instant::now();
|
last_weather_fetch = Instant::now();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Always check birthdays on every refresh loop
|
let upcoming_birthdays = get_upcoming_birthdays();
|
||||||
upcoming_birthdays = get_upcoming_birthdays();
|
let countdowns = get_countdowns();
|
||||||
|
|
||||||
sys.refresh_all();
|
sys.refresh_all();
|
||||||
networks.refresh();
|
networks.refresh();
|
||||||
@@ -589,10 +668,10 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
|
|||||||
print_line(&mut stdout, "Weather Today", &weather.today_summary, FG_MAIN)?;
|
print_line(&mut stdout, "Weather Today", &weather.today_summary, FG_MAIN)?;
|
||||||
print_line(&mut stdout, "Weather Tomorrow", &weather.tomorrow_summary, FG_MAIN)?;
|
print_line(&mut stdout, "Weather Tomorrow", &weather.tomorrow_summary, FG_MAIN)?;
|
||||||
|
|
||||||
// Calendar & Birthdays Section
|
// Bottom Section (Calendar, Events, Days Until)
|
||||||
execute!(stdout, SetForegroundColor(FG_MAIN))?;
|
execute!(stdout, SetForegroundColor(FG_MAIN))?;
|
||||||
println!("-----------------------------------------------------------------------------------------------");
|
println!("-----------------------------------------------------------------------------------------------");
|
||||||
render_calendar_and_birthdays(&mut stdout, &upcoming_birthdays)?;
|
render_bottom_section(&mut stdout, &upcoming_birthdays, &countdowns)?;
|
||||||
|
|
||||||
execute!(stdout, SetForegroundColor(FG_MAIN))?;
|
execute!(stdout, SetForegroundColor(FG_MAIN))?;
|
||||||
println!("===============================================================================================");
|
println!("===============================================================================================");
|
||||||
|
|||||||
Reference in New Issue
Block a user