Add weather API
Some checks failed
ci/woodpecker/push/woodpecker Pipeline failed

This commit is contained in:
Isaac Mills 2025-07-28 15:15:55 -06:00
parent 3a6b9713f9
commit 265f8d8dc3
Signed by: fnmain
GPG key ID: B67D7410F33A0F61
5 changed files with 154 additions and 44 deletions

View file

@ -1,18 +1,24 @@
use std::{
future::Future,
rc::Rc,
sync::atomic::{AtomicUsize, Ordering},
sync::{
atomic::{AtomicUsize, Ordering},
mpsc::{self, Receiver, Sender},
},
};
use rand::{rngs::OsRng, seq::IndexedRandom, TryRngCore};
#[cfg(target_arch = "wasm32")]
use ratzilla::ratatui;
use dotenvy_macro::dotenv;
use ratatui::{
layout::{Constraint, Direction, Layout, Rect},
style::{Color, Style, Stylize},
widgets::{Block, Borders, Paragraph, Tabs},
Frame,
};
use serde::Deserialize;
use tachyonfx::{fx, Effect, Interpolation, Shader};
use web_time::Instant;
@ -61,12 +67,43 @@ impl Shader for SelectedTab {
fn filter(&mut self, _filter: tachyonfx::CellFilter) {}
}
pub struct App {
pub trait AppExecutor {
fn execute<T: 'static>(&self, future: impl Future<Output = T> + 'static, sender: Sender<T>);
}
pub struct Pending<T> {
rx: Receiver<T>,
resolved: Option<T>,
}
impl<T> Pending<T> {
pub fn new(rx: Receiver<T>) -> Self {
Self { rx, resolved: None }
}
pub fn resolved(&mut self) -> &Option<T> {
if self.resolved.is_some() {
return &self.resolved;
}
self.resolved = self.rx.try_recv().ok();
&self.resolved
}
}
#[derive(Deserialize, Debug)]
pub struct WeatherInfo {
name: String,
}
pub struct App<E: AppExecutor> {
tabs: [&'static str; 3],
transition_instant: Instant,
selected_tab: SelectedTab,
password_locker: PasswordLocker,
current_effect: Effect,
weather: Pending<WeatherInfo>,
executor: E,
}
const SPLASH: &str = r#"
@ -109,14 +146,27 @@ const TABS: [&'static str; 10] = [
"Goobers",
];
impl App {
pub fn new() -> Self {
impl<E: AppExecutor> App<E> {
pub fn new(executor: E) -> Self {
let (weather_tx, weather_rx) = mpsc::channel();
executor.execute(async move {
let weather_info: WeatherInfo = reqwest::get(concat!("https://api.openweathermap.org/data/2.5/weather?lat=40.7660851712019&lon=-111.89066476757807&appid=", dotenv!("OPENWEATHERMAP_API_KEY")))
.await
.unwrap()
.json()
.await
.unwrap();
weather_info
},
weather_tx);
Self {
tabs: TABS.choose_multiple_array(&mut OsRng.unwrap_err()).unwrap(),
transition_instant: Instant::now(),
selected_tab: SelectedTab::default(),
password_locker: PasswordLocker::default(),
current_effect: fx::fade_from_fg(Color::Black, (0, Interpolation::CircIn)),
weather: Pending::new(weather_rx),
executor,
}
}
@ -162,13 +212,32 @@ impl App {
}
pub fn draw_first_tab(&mut self, frame: &mut Frame, layout: Rect) {
let layout = Layout::new(
Direction::Horizontal,
[Constraint::Ratio(1, 2), Constraint::Ratio(1, 2)],
)
.split(layout);
frame.render_widget(
Paragraph::new(
self.weather
.resolved()
.as_ref()
.map(|weather| weather.name.as_str())
.unwrap_or("Loading"),
)
.alignment(ratatui::layout::Alignment::Center)
.fg(Color::Rgb(226, 190, 89))
.bg(Color::Black)
.block(Block::new().title("Weather").borders(Borders::all())),
layout[0],
);
frame.render_widget(
Paragraph::new(SPLASH)
.alignment(ratatui::layout::Alignment::Center)
.fg(Color::Rgb(226, 190, 89))
.bg(Color::Black)
.block(Block::new().borders(Borders::all())),
layout,
layout[1],
);
}