p2p_chat/ui/terminal/
controller.rs

1//! This module defines the `TerminalUI` controller, which manages the main
2//! loop for the terminal user interface.
3use std::sync::Arc;
4
5use anyhow::Result;
6use chrono::{Duration, Utc};
7use tokio::sync::mpsc;
8use tracing::{debug, error};
9
10use crate::cli::commands::Node;
11use crate::logging::LogBuffer;
12use crate::types::Message;
13use crate::ui::{ChatMode, LogMode, UIAction, UIEvent, UIState};
14
15/// Manages the terminal user interface, including state, rendering, and event handling.
16pub struct TerminalUI {
17    /// The current state of the user interface.
18    pub(super) state: UIState,
19    /// The chat mode specific logic and state.
20    pub(super) chat_mode: ChatMode,
21    /// The log mode specific logic and state.
22    pub(super) log_mode: LogMode,
23    /// Receiver for UI events from various parts of the application.
24    pub(super) event_rx: mpsc::UnboundedReceiver<UIEvent>,
25    /// Sender for dispatching UI actions.
26    pub(super) action_tx: mpsc::UnboundedSender<UIAction>,
27    /// An optional reference to the application's core `Node`.
28    pub(super) node: Option<Arc<Node>>,
29    /// An optional reference to the `LogBuffer`.
30    pub(super) log_buffer: Option<Arc<LogBuffer>>,
31}
32
33impl TerminalUI {
34    /// Creates a new `TerminalUI` instance.
35    ///
36    /// # Arguments
37    ///
38    /// * `event_rx` - The receiver for `UIEvent`s.
39    /// * `action_tx` - The sender for `UIAction`s.
40    pub fn new(
41        event_rx: mpsc::UnboundedReceiver<UIEvent>,
42        action_tx: mpsc::UnboundedSender<UIAction>,
43    ) -> Self {
44        Self {
45            state: UIState::new(),
46            chat_mode: ChatMode::new(),
47            log_mode: LogMode::new(),
48            event_rx,
49            action_tx,
50            node: None,
51            log_buffer: None,
52        }
53    }
54
55    /// Sets the application's core `Node`.
56    pub fn set_node(&mut self, node: Arc<Node>) {
57        self.node = Some(node);
58    }
59
60    /// Sets the `LogBuffer` for the UI.
61    pub fn set_log_buffer(&mut self, log_buffer: Arc<LogBuffer>) {
62        self.log_buffer = Some(log_buffer);
63    }
64
65    /// Updates the list of friends in the chat mode's completer.
66    pub fn update_friends(&mut self, friends: Vec<String>) {
67        self.chat_mode.update_friends(friends);
68    }
69
70    /// Updates the list of discovered peers in the chat mode's completer.
71    pub fn update_discovered_peers(&mut self, peers: Vec<String>) {
72        self.chat_mode.update_discovered_peers(peers);
73    }
74
75    /// Preloads initial messages into the UI state.
76    ///
77    /// This is typically used to display recent message history on startup.
78    ///
79    /// # Arguments
80    ///
81    /// * `messages` - A `Vec` of `Message`s to preload.
82    pub fn preload_messages(&mut self, messages: Vec<Message>) {
83        let count = messages.len();
84        let earliest_timestamp = messages
85            .iter()
86            .filter_map(|msg| chrono::DateTime::<Utc>::from_timestamp_millis(msg.timestamp))
87            .min();
88
89        self.state.replace_messages(messages);
90
91        if count > 0 {
92            if let Some(earliest) = earliest_timestamp {
93                let header_ts = earliest
94                    .checked_sub_signed(Duration::milliseconds(1))
95                    .unwrap_or(earliest);
96                self.state.chat_messages.push((
97                    header_ts,
98                    format!(
99                        "__HISTORY_OUTPUT__History: last {} message{}",
100                        count,
101                        if count == 1 { "" } else { "s" }
102                    ),
103                ));
104            }
105        }
106    }
107
108    /// Runs the main event loop for the terminal UI.
109    ///
110    /// This function continuously listens for UI events, handles them,
111    /// and re-renders the terminal display.
112    ///
113    /// # Errors
114    ///
115    /// Returns an error if terminal initialization fails or if an
116    /// unrecoverable error occurs within the event handling or rendering loop.
117    pub async fn run(&mut self) -> Result<()> {
118        self.initialize_terminal()?;
119
120        debug!("Starting terminal UI loop");
121
122        loop {
123            if let Some(event) = self.event_rx.recv().await {
124                if let Err(e) = self.handle_event(event).await {
125                    error!("Error handling UI event: {}", e);
126                }
127            }
128
129            self.render()?;
130        }
131    }
132}