use chrono::Utc; use ratatui::crossterm::event::{self, poll, EnableMouseCapture, Event, KeyCode}; use ratatui::crossterm::execute; use ratatui::crossterm::terminal::{enable_raw_mode, EnterAlternateScreen}; use ratatui::crossterm::event::DisableMouseCapture; use ratatui::crossterm::terminal::{disable_raw_mode, LeaveAlternateScreen}; use ratatui::prelude::{Backend, CrosstermBackend}; use ratatui::Terminal; use sgp4::{julian_years_since_j2000, MinutesSinceEpoch}; use std::f32::consts::PI; use std::io::{self, Result}; use std::sync::{Arc, Mutex}; use std::time::Duration; mod app; mod ui; mod location; #[cfg(feature = "logging")] mod logging; use app::*; #[tokio::main] async fn main() -> Result<()> { #[cfg(feature = "logging")] let _ = logging::initialize_logging(); enable_raw_mode()?; let mut stderr = io::stderr(); // This is a special case. Normally using stdout is fine execute!(stderr, EnterAlternateScreen, EnableMouseCapture)?; let backend = CrosstermBackend::new(stderr); let mut terminal = Terminal::new(backend)?; // create app and run it let app = Arc::new(Mutex::new(App::new())); let location_shared_app = Arc::clone(&app); let location_shared_app2 = Arc::clone(&app); let mut runtime_shared_app = Arc::clone(&app); let get_constant = tokio::task::spawn(async move { location_shared_app.lock().unwrap().getting_location = true; let constant = location::get_celerak_data().await; if let (Some(cons),Some(epoch)) = constant { location_shared_app.lock().unwrap().iss_constant = Some(cons); location_shared_app.lock().unwrap().iss_constant_epoch = Some(epoch); } location_shared_app.lock().unwrap().getting_location = false; }); let _ = get_constant; let forever = tokio::task::spawn(async move { let mut interval = tokio::time::interval(Duration::from_secs(2)); loop { interval.tick().await; if location_shared_app2.lock().unwrap().iss_constant.is_some() { let now = Utc::now(); let d_since_j2000 = julian_years_since_j2000(&now.naive_utc()) * 365.25; let rad_diff_earth_rotation = location::earth_rotation_angle(d_since_j2000); let time_diff = now - location_shared_app2.lock().unwrap().iss_constant_epoch.unwrap(); let prediction = location_shared_app2.lock().unwrap().iss_constant.as_ref().unwrap() .propagate(MinutesSinceEpoch(time_diff.num_seconds() as f64 / 60.0)).unwrap(); let pred_spherical = location::polar_loc_transformer(prediction.position); let long_value = if pred_spherical[0]-(rad_diff_earth_rotation as f32)*180.0/PI < 180.0 { pred_spherical[0]-(rad_diff_earth_rotation as f32)*180.0/PI + 360.0 } else { pred_spherical[0]-(rad_diff_earth_rotation as f32)*180.0/PI }; location_shared_app2.lock().unwrap().iss_location = Some(location::FLocation { latitude: pred_spherical[1], longitude: long_value }); } } ; }); let _ = forever; let _res = run_app(&mut terminal, &mut runtime_shared_app).await; // restore terminal disable_raw_mode()?; execute!( terminal.backend_mut(), LeaveAlternateScreen, DisableMouseCapture )?; terminal.show_cursor()?; Ok(()) } async fn run_app (terminal: &mut Terminal, mut app: &mut Arc>) -> Result { loop { let _ = terminal.draw(|f| ui::ui(f, &mut app)); if poll(Duration::from_millis(15))? { if let Event::Key(key) = event::read()? { let app_shared = Arc::new(&app); let mut app_shared = app_shared.lock().unwrap(); if key.kind == event::KeyEventKind::Release { continue; } match app_shared.current_screen { app::CurrentScreen::Map => match key.code { KeyCode::Char('c') => { app_shared.current_screen = CurrentScreen::CityChoice; } KeyCode::Char('q') => { app_shared.current_screen = CurrentScreen::Exiting; } _ => {} } app::CurrentScreen::Exiting => match key.code { KeyCode::Char('n') | KeyCode::Esc => { app_shared.current_screen = CurrentScreen::Map } KeyCode::Char('y') | KeyCode::Char('q') => { return Ok(false) } _ => {} } //weird behaviour with the ListState being 1 longer then the list, resulting in //index overflow for the cities list. do not know why. yes even after rendering //when the ListState was supposed to update app::CurrentScreen::CityChoice => match key.code { KeyCode::Up => { if app_shared.cities.state.selected() == Some(0) { app_shared.cities.state.select(Some(CITIES.len()-1)); } else { app_shared.cities.state.scroll_up_by(1); } }, KeyCode::Down => { if app_shared.cities.state.selected() == Some(app_shared.cities.cities.len()-1) { app_shared.cities.state.select(Some(0)); } else { app_shared.cities.state.scroll_down_by(1); } } KeyCode::Enter => app_shared.current_screen = CurrentScreen::Map, KeyCode::Esc => app_shared.current_screen = CurrentScreen::Map, KeyCode::Char('q') => app_shared.current_screen = CurrentScreen::Map, _ => {} } } } } } }