use std::{ collections::{BTreeMap, VecDeque}, io, net::SocketAddr, }; use crossterm::event::{Event, EventStream, KeyCode, KeyEvent, KeyEventKind}; use futures::{FutureExt, StreamExt}; use joecalsend::JoecalState; use ratatui::{ DefaultTerminal, buffer::Buffer, layout::Rect, style::Stylize, symbols::border, text::{Line, Text}, widgets::{Block, Paragraph, Widget}, }; pub mod ui; pub type Peers = BTreeMap; pub struct App { pub state: JoecalState, pub screen: VecDeque, pub events: EventStream, // addr -> (alias, fingerprint) pub peers: Peers, } #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum CurrentScreen { Main, Sending, Receiving, Stopping, } impl App { pub fn new(state: JoecalState) -> Self { App { state, screen: VecDeque::from([CurrentScreen::Main]), peers: Default::default(), events: Default::default(), } } pub async fn run(&mut self, terminal: &mut DefaultTerminal) -> io::Result<()> { loop { terminal.draw(|frame| self.draw(frame))?; self.handle_events().await?; if let Some(&top) = self.screen.back() && top == CurrentScreen::Stopping { self.state.stop().await; break; } let peers = self.state.peers.lock().await; self.peers.clear(); peers.iter().for_each(|(k, v)| { // k is fingerprint, v is addr, device let addr = v.0; let alias = v.1.alias.clone(); let fingerprint = k.clone(); self.peers.insert(addr, (alias, fingerprint)); }); } Ok(()) } async fn handle_events(&mut self) -> io::Result<()> { tokio::select! { event = self.events.next().fuse() => { if let Some(Ok(evt)) = event { match evt { Event::Key(key) if key.kind == KeyEventKind::Press => self.handle_key_event(key), Event::Mouse(_) => {} Event::Resize(_, _) => {} _ => {} } } } _ = tokio::time::sleep(tokio::time::Duration::from_millis(100)) => {} } Ok(()) } fn handle_key_event(&mut self, key_event: KeyEvent) { match key_event.code { KeyCode::Char('q') => self.exit(), KeyCode::Char('s') => self.send(), KeyCode::Char('r') => self.recv(), KeyCode::Esc => self.pop(), _ => {} } } fn exit(&mut self) { self.screen.clear(); self.screen.push_back(CurrentScreen::Stopping); } fn send(&mut self) { self.screen.clear(); self.screen.push_back(CurrentScreen::Sending); } fn recv(&mut self) { self.screen.clear(); self.screen.push_back(CurrentScreen::Receiving); } fn pop(&mut self) { if self.screen.pop_back().is_none() { self.screen.push_back(CurrentScreen::Main); } } } impl Widget for &App { fn render(self, area: Rect, buf: &mut Buffer) { let title = Line::from(" Joecalsend ".bold()); let instructions = Line::from(vec![ " Send ".into(), "".blue().bold(), " Receive ".into(), "".blue().bold(), " Discover ".into(), "".blue().bold(), " Quit ".into(), " ".blue().bold(), ]); let block = Block::bordered() .title(title.centered()) .title_bottom(instructions.centered()) .border_set(border::THICK); let current_screen = format!( "{:?}", self.screen.back().cloned().unwrap_or(CurrentScreen::Main) ); let text = Text::from(Line::from(current_screen.yellow())); Paragraph::new(text) .centered() .block(block) .render(area, buf); } }