jocalsend 1.6.180339887

A TUI for LocalSend
Documentation
use std::{collections::BTreeMap, net::SocketAddr};

use crossterm::event::{Event, EventStream, KeyEventKind};
use futures::{FutureExt, StreamExt};
use jocalsend::{JocalEvent, JocalService, ReceiveRequest, error::Result};
use julid::Julid;
use ratatui::{
    Frame,
    widgets::{ListState, TableState},
};
use ratatui_explorer::FileExplorer;
use tokio::sync::mpsc::UnboundedReceiver;
use tui_input::Input;

pub mod widgets;

mod file_finder;
use file_finder::FileFinder;

mod handle;

#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Peer {
    pub alias: String,
    pub fingerprint: String,
    pub addr: SocketAddr,
}

pub struct App {
    pub service: JocalService,
    pub terminal_events: EventStream,
    pub peers: Vec<Peer>,
    pub peer_state: ListState,
    pub receive_requests: BTreeMap<Julid, ReceiveRequest>,
    screen: Vec<CurrentScreen>,
    receiving_state: TableState,
    // for getting messages back from the web server or web client about things we've done; the
    // other end is held by the service
    jocal_event_rx: UnboundedReceiver<JocalEvent>,
    file_finder: FileFinder,
    text: Option<String>,
    input: Input,
}

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum CurrentScreen {
    Main,
    Sending(SendingScreen),
    Receiving,
    Stopping,
    Logging,
    Help,
}

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum SendingScreen {
    Files(FileMode),
    Peers,
    Text,
}

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum FileMode {
    Picking,
    Fuzzy,
}

impl App {
    pub fn new(service: JocalService, event_listener: UnboundedReceiver<JocalEvent>) -> Self {
        App {
            service,
            jocal_event_rx: event_listener,
            screen: vec![CurrentScreen::Main],
            file_finder: FileFinder::new().expect("could not create file explorer"),
            text: None,
            terminal_events: Default::default(),
            peers: Default::default(),
            peer_state: Default::default(),
            receive_requests: Default::default(),
            receiving_state: Default::default(),
            input: Default::default(),
        }
    }

    pub async fn handle_events(&mut self) -> Result<()> {
        tokio::select! {
            jocal_event = self.jocal_event_rx.recv().fuse() => {
                if let Some(event) = jocal_event {
                    log::trace!("got JocalEvent {event:?}");
                    match event {
                        JocalEvent::ReceiveRequest { id, request } => {
                            self.receive_requests.insert(id, request);
                        }
                        JocalEvent::Cancelled { session_id: id } | JocalEvent::ReceivedInbound(id) => {
                            self.receive_requests.remove(&id);
                        }
                        JocalEvent::SendApproved(id) => log::info!("remote recipient approved outbound transfer {id}"),
                        JocalEvent::SendDenied => log::warn!("outbound transfer request has been denied"),
                        JocalEvent::SendSuccess { content, session: _session } => log::info!("successfully sent {content}"),
                        JocalEvent::SendFailed { error } => log::error!("could not send content: {error}"),
                        JocalEvent::Tick => {}
                    }
                }
            }

            terminal_event = self.terminal_events.next().fuse() => {
                if let Some(Ok(evt)) = terminal_event {
                    match evt {
                        Event::Key(key)
                            if key.kind == KeyEventKind::Press
                            => self.handle_key_event(key, evt).await,
                        Event::Mouse(_) => {}
                        Event::Resize(_, _) => {}
                        _ => {}
                    }
                }
            }
        }

        Ok(())
    }

    pub fn input(&mut self) -> &mut Input {
        &mut self.input
    }

    pub fn files(&mut self) -> &mut FileExplorer {
        &mut self.file_finder.explorer
    }

    pub fn text(&mut self) -> &mut Option<String> {
        &mut self.text
    }

    pub fn screen(&self) -> CurrentScreen {
        *self.screen.last().unwrap()
    }
    pub fn screen_mut(&mut self) -> &mut CurrentScreen {
        self.screen.last_mut().unwrap()
    }

    pub fn draw(&mut self, frame: &mut Frame) {
        frame.render_widget(self, frame.area());
    }
}