gm_lib/tui/app/pages/
send_message.rs1use 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}, 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 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}