Skip to main content

gm_lib/tui/app/pages/
send_message.rs

1use crate::disk::DiskInterface;
2use crate::network::NetworkStore;
3use crate::tui::app::widgets::address_book_popup::AddressBookPopup;
4use crate::tui::app::widgets::form::FormItemIndex;
5use crate::tui::app::widgets::networks_popup::NetworksPopup;
6use crate::tui::app::widgets::tx_popup::TxPopup;
7use crate::tui::app::SharedState;
8use crate::tui::{
9    app::widgets::form::{Form, FormWidget}, // <- Using your custom form system
10    events::Event,
11    traits::{Component, HandleResult},
12};
13
14use super::address_book::AddressBookMenuItem;
15use crate::Result;
16
17use alloy::primitives::Bytes;
18use alloy::rpc::types::{TransactionInput, TransactionRequest};
19use ratatui::layout::Rect;
20use std::sync::mpsc;
21use std::sync::{atomic::AtomicBool, Arc};
22use strum::EnumIter;
23
24#[derive(EnumIter, PartialEq)]
25pub enum FormItem {
26    Heading,
27    To,
28    Message,
29    Network,
30    SendMessageButton,
31}
32impl FormItemIndex for FormItem {
33    fn index(self) -> usize {
34        self as usize
35    }
36}
37impl TryFrom<FormItem> for FormWidget {
38    type Error = crate::Error;
39    fn try_from(value: FormItem) -> crate::Result<Self> {
40        let widget = match value {
41            FormItem::Heading => FormWidget::Heading("Send a Message"),
42            FormItem::To => FormWidget::InputBox {
43                label: "To",
44                text: String::new(),
45                empty_text: Some("<press SPACE to select from address book>"),
46                currency: None,
47            },
48            FormItem::Message => FormWidget::InputBox {
49                label: "Message",
50                text: String::new(),
51                empty_text: Some("Type message to send"),
52                currency: None,
53            },
54            FormItem::Network => FormWidget::DisplayBox {
55                label: "Network",
56                text: String::new(),
57                empty_text: Some("<press SPACE to select network>"),
58            },
59            FormItem::SendMessageButton => FormWidget::Button {
60                label: "Send Message",
61            },
62        };
63        Ok(widget)
64    }
65}
66
67pub struct SendMessagePage {
68    pub form: Form<FormItem>,
69    pub address_book_popup: AddressBookPopup,
70    pub networks_popup: NetworksPopup,
71    pub tx_popup: TxPopup,
72}
73
74impl SendMessagePage {
75    pub fn new() -> crate::Result<Self> {
76        Ok(Self {
77            form: Form::init(|_| Ok(()))?,
78            address_book_popup: AddressBookPopup::default(),
79            networks_popup: NetworksPopup::default(),
80            tx_popup: TxPopup::default(),
81        })
82    }
83}
84
85impl Component for SendMessagePage {
86    fn set_focus(&mut self, focus: bool) {
87        self.form.set_form_focus(focus);
88    }
89
90    fn handle_event(
91        &mut self,
92        event: &Event,
93        area: Rect,
94        tr: &mpsc::Sender<Event>,
95        sd: &Arc<AtomicBool>,
96        ss: &SharedState,
97    ) -> Result<HandleResult> {
98        let mut result = HandleResult::default();
99
100        #[allow(clippy::single_match)]
101        match event {
102            Event::ConfigUpdate => {
103                let network_name = self.form.get_text(FormItem::Network);
104                let network_store = NetworkStore::load()?;
105                let network = network_store
106                    .get_by_name(network_name)
107                    .ok_or(crate::Error::NetworkNotFound(network_name.to_string()))?;
108
109                if network.is_testnet != ss.testnet_mode {
110                    self.form.get_text_mut(FormItem::Network).clear();
111                }
112            }
113            _ => {}
114        }
115
116        if self.address_book_popup.is_open() {
117            result.merge(self.address_book_popup.handle_event(event, |entry| {
118                let to_address = self.form.get_text_mut(FormItem::To);
119                *to_address = entry.address_unwrap().to_string();
120                self.form.advance_cursor();
121                Ok(())
122            })?);
123        } else if self.networks_popup.is_open() {
124            result.merge(self.networks_popup.handle_event(event, |network| {
125                let network_str = self.form.get_text_mut(FormItem::Network);
126                *network_str = network.name.clone();
127                self.form.advance_cursor();
128                Ok(())
129            })?);
130        } else if self.tx_popup.is_open() {
131            let r = self.tx_popup.handle_event(
132                (event, area, tr, sd, ss),
133                |_| Ok(()),
134                |_| Ok(()),
135                || Ok(()),
136                || Ok(()),
137            )?;
138            result.merge(r);
139        } else {
140            // Handle form events
141            if self.form.is_focused(FormItem::To)
142                && self.form.get_text(FormItem::To).is_empty()
143                && event.is_space_or_enter_pressed()
144            {
145                self.address_book_popup.open();
146                self.address_book_popup
147                    .set_items(Some(AddressBookMenuItem::get_menu(
148                        false,
149                        ss.recent_addresses.clone(),
150                    )?));
151            } else if self.form.is_focused(FormItem::Network) && event.is_space_or_enter_pressed() {
152                self.networks_popup.open();
153                self.networks_popup
154                    .set_items(Some(NetworkStore::load_networks(ss.testnet_mode)?));
155            } else {
156                self.form.handle_event(
157                    event,
158                    |_, _| Ok(()),
159                    |label, form| {
160                        if label == FormItem::SendMessageButton {
161                            let to = form.get_text(FormItem::To);
162                            let message = form.get_text(FormItem::Message);
163                            let network_name = form.get_text(FormItem::Network);
164                            if message.is_empty() {
165                                return Err("Message cannot be empty".into());
166                            }
167
168                            self.tx_popup.set_tx_req(
169                                NetworkStore::from_name(network_name)?,
170                                TransactionRequest::default().to(to.parse()?).input(
171                                    TransactionInput::from(Bytes::from(
172                                        message.to_owned().into_bytes(),
173                                    )),
174                                ),
175                            );
176                            self.tx_popup.open();
177                        }
178
179                        Ok(())
180                    },
181                )?;
182            }
183        }
184
185        Ok(result)
186    }
187
188    fn render_component(
189        &self,
190        area: Rect,
191        buf: &mut ratatui::prelude::Buffer,
192        shared_state: &SharedState,
193    ) -> ratatui::prelude::Rect
194    where
195        Self: Sized,
196    {
197        self.form.render(area, buf, &shared_state.theme);
198
199        self.address_book_popup
200            .render(area, buf, &shared_state.theme.popup());
201
202        self.networks_popup
203            .render(area, buf, &shared_state.theme.popup());
204
205        self.tx_popup.render(area, buf, &shared_state.theme.popup());
206
207        area
208    }
209}