use std::cmp::max;
use std::sync::Arc;
use std::sync::Mutex;
use crossterm::event::Event;
use crossterm::event::KeyCode;
use crossterm::event::KeyEventKind;
use neptune_cash::application::config::network::Network;
use neptune_cash::application::rpc::auth;
use neptune_cash::state::wallet::address::KeyType;
use neptune_cash::state::wallet::address::ReceivingAddress;
use ratatui::layout::Alignment;
use ratatui::layout::Margin;
use ratatui::style::Color;
use ratatui::style::Style;
use ratatui::text::Line;
use ratatui::text::Span;
use ratatui::text::Text;
use ratatui::widgets::Block;
use ratatui::widgets::Borders;
use ratatui::widgets::Paragraph;
use ratatui::widgets::Widget;
use tarpc::context;
use super::dashboard_app::ConsoleIO;
use super::dashboard_app::DashboardEvent;
use super::overview_screen::VerticalRectifier;
use super::screen::Screen;
use crate::dashboard_rpc_client::DashboardRpcClient;
#[derive(Debug, Clone)]
pub struct ReceiveScreen {
active: bool,
fg: Color,
bg: Color,
in_focus: bool,
data: Arc<std::sync::Mutex<Option<ReceivingAddress>>>,
server: Arc<DashboardRpcClient>,
generating: Arc<Mutex<bool>>,
escalatable_event: Arc<std::sync::Mutex<Option<DashboardEvent>>>,
network: Network,
token: auth::Token,
}
impl ReceiveScreen {
pub fn new(rpc_server: Arc<DashboardRpcClient>, network: Network, token: auth::Token) -> Self {
Self {
active: false,
fg: Color::Gray,
bg: Color::Black,
in_focus: false,
data: Arc::new(Mutex::new(None)),
server: rpc_server,
generating: Arc::new(Mutex::new(false)),
escalatable_event: Arc::new(std::sync::Mutex::new(None)),
network,
token,
}
}
fn populate_receiving_address_async(
&self,
rpc_client: Arc<DashboardRpcClient>,
token: auth::Token,
data: Arc<Mutex<Option<ReceivingAddress>>>,
) {
if data.lock().unwrap().is_none() {
let escalatable_event = self.escalatable_event.clone();
tokio::spawn(async move {
let receiving_address = rpc_client
.latest_address(context::current(), token, KeyType::Generation)
.await
.unwrap()
.unwrap();
*data.lock().unwrap() = Some(receiving_address);
*escalatable_event.lock().unwrap() = Some(DashboardEvent::RefreshScreen);
});
}
}
fn generate_new_receiving_address_async(
&self,
rpc_client: Arc<DashboardRpcClient>,
token: auth::Token,
data: Arc<Mutex<Option<ReceivingAddress>>>,
generating: Arc<Mutex<bool>>,
) {
let escalatable_event = self.escalatable_event.clone();
tokio::spawn(async move {
*generating.lock().unwrap() = true;
let receiving_address = rpc_client
.next_receiving_address(context::current(), token, KeyType::Generation)
.await
.unwrap()
.unwrap();
*data.lock().unwrap() = Some(receiving_address);
*generating.lock().unwrap() = false;
*escalatable_event.lock().unwrap() = Some(DashboardEvent::RefreshScreen);
});
}
pub fn handle(&mut self, event: DashboardEvent) -> Option<DashboardEvent> {
let mut escalate_event = None;
if self.in_focus {
if let DashboardEvent::ConsoleEvent(Event::Key(key)) = event {
if key.kind == KeyEventKind::Press {
match key.code {
KeyCode::Enter => {
self.generate_new_receiving_address_async(
self.server.clone(),
self.token,
self.data.clone(),
self.generating.clone(),
);
escalate_event = Some(DashboardEvent::RefreshScreen);
}
KeyCode::Char('c') => {
if let Some(address) = self.data.lock().unwrap().as_ref() {
return Some(DashboardEvent::ConsoleMode(
ConsoleIO::InputRequested(format!(
"{}\n\n",
address.to_bech32m(self.network).unwrap()
)),
));
}
}
_ => {
escalate_event = Some(event);
}
}
}
}
}
escalate_event
}
}
impl Screen for ReceiveScreen {
fn activate(&mut self) {
self.active = true;
let server_arc = self.server.clone();
let data_arc = self.data.clone();
let token = self.token;
self.populate_receiving_address_async(server_arc, token, data_arc);
}
fn deactivate(&mut self) {
self.active = false;
}
fn focus(&mut self) {
self.fg = Color::White;
self.in_focus = true;
}
fn unfocus(&mut self) {
self.fg = Color::Gray;
self.in_focus = false;
}
fn escalatable_event(&self) -> Arc<std::sync::Mutex<Option<DashboardEvent>>> {
self.escalatable_event.clone()
}
}
impl Widget for ReceiveScreen {
fn render(self, area: ratatui::layout::Rect, buf: &mut ratatui::buffer::Buffer) {
let style: Style = if self.in_focus {
Style::default().fg(Color::LightCyan).bg(self.bg)
} else {
Style::default().fg(Color::Gray).bg(self.bg)
};
Block::default()
.borders(Borders::ALL)
.title("Receive")
.style(style)
.render(area, buf);
let style = Style::default().bg(self.bg).fg(self.fg);
let inner = area.inner(Margin {
vertical: 1,
horizontal: 1,
});
let mut vrecter = VerticalRectifier::new(inner);
let receiving_address = self.data.lock().unwrap().to_owned();
let (mut address, address_abbrev) = match receiving_address {
Some(addr) => (
addr.to_bech32m(self.network).unwrap(),
addr.to_bech32m_abbreviated(self.network).unwrap(),
),
None => ("-".to_string(), "-".to_string()),
};
let width = max(0, inner.width as isize - 2) as usize;
if width > 0 {
let mut address_lines = vec![];
let address_abbrev_rect = vrecter.next(1 + 2);
let address_abbrev_display = Paragraph::new(Text::from(address_abbrev))
.style(style)
.block(Block::default().borders(Borders::ALL).title(Span::styled(
"Receiving Address (abbreviated)",
Style::default(),
)))
.alignment(Alignment::Left);
address_abbrev_display.render(address_abbrev_rect, buf);
vrecter.next(1);
while address.len() > width {
let (line, remainder) = address.split_at(width);
address_lines.push(line.to_owned());
address = remainder.to_owned();
}
address_lines.push(address);
let address_rect = vrecter.next((address_lines.len() + 2).try_into().unwrap());
if address_rect.height > 0 {
let address_display = Paragraph::new(Text::from(address_lines.join("\n")))
.style(style)
.block(
Block::default()
.borders(Borders::ALL)
.title(Span::styled("Receiving Address", Style::default())),
)
.alignment(Alignment::Left);
address_display.render(address_rect, buf);
}
if *self.generating.lock().unwrap() {
let generating_text =
Paragraph::new(Span::from("Generating ...")).alignment(Alignment::Left);
generating_text.render(vrecter.next(1), buf);
} else {
let action = if self.in_focus {
"generate a new address"
} else {
"focus"
};
let instructions = Line::from(vec![
Span::from("Press "),
Span::styled("Enter ↵", Style::default().fg(Color::LightCyan)),
Span::from(" to "),
Span::from(action),
Span::from("."),
]);
let style = Style::default().fg(self.fg);
let generate_instructions = Paragraph::new(instructions).style(style);
generate_instructions.render(vrecter.next(1), buf);
}
if self.in_focus {
let style = Style::default().fg(self.fg);
let instructions = Line::from(vec![
Span::from("Press "),
Span::styled(
"C",
if self.in_focus {
Style::default().fg(Color::LightCyan)
} else {
style
},
),
Span::from(" display in console mode."),
]);
let generate_instructions = Paragraph::new(instructions).style(style);
generate_instructions.render(vrecter.next(1), buf);
}
}
}
}