diff --git a/assets/icons/copy.svg b/assets/icons/copy.svg new file mode 100644 index 0000000..f2cc9df --- /dev/null +++ b/assets/icons/copy.svg @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/src/bridge.rs b/src/bridge.rs index 842cabf..f8cc574 100644 --- a/src/bridge.rs +++ b/src/bridge.rs @@ -15,6 +15,8 @@ pub enum CoreUIMsg { Sending, SendSuccess, SendFailure(String), + ReceiveInvoiceGenerating, + ReceiveInvoiceGenerated(Bolt11Invoice), ReceiveSuccess, ReceiveFailed(String), BalanceUpdated(Amount), diff --git a/src/components/button.rs b/src/components/button.rs index cffc5bd..e07e923 100644 --- a/src/components/button.rs +++ b/src/components/button.rs @@ -20,6 +20,7 @@ pub enum SvgIcon { Settings, Squirrel, UpRight, + Copy, } fn map_icon(icon: SvgIcon) -> Svg<'static, Theme> { @@ -33,6 +34,7 @@ fn map_icon(icon: SvgIcon) -> Svg<'static, Theme> { SvgIcon::Settings => Svg::from_path("assets/icons/settings.svg"), SvgIcon::Squirrel => Svg::from_path("assets/icons/squirrel.svg"), SvgIcon::UpRight => Svg::from_path("assets/icons/up_right.svg"), + SvgIcon::Copy => Svg::from_path("assets/icons/copy.svg"), } } diff --git a/src/core.rs b/src/core.rs index c252e98..ef9daa8 100644 --- a/src/core.rs +++ b/src/core.rs @@ -224,8 +224,17 @@ pub fn run_core() -> Subscription { } } UICoreMsg::Receive(amount) => { - if let Err(e) = core.receive(amount).await { - error!("Error receiving: {e}"); + core.msg(CoreUIMsg::ReceiveInvoiceGenerating).await; + match core.receive(amount).await { + Err(e) => { + core.msg(CoreUIMsg::ReceiveFailed(e.to_string())).await; + } + Ok(invoice) => { + core.msg(CoreUIMsg::ReceiveInvoiceGenerated( + invoice.clone(), + )) + .await; + } } } } diff --git a/src/main.rs b/src/main.rs index b5decea..48c35a2 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,13 +1,14 @@ use core::run_core; use fedimint_core::Amount; use fedimint_ln_common::lightning_invoice::Bolt11Invoice; +use routes::Route; use std::str::FromStr; use std::sync::Arc; use bridge::CoreUIMsg; use iced::subscription::Subscription; use iced::widget::row; -use iced::{program, Color}; +use iced::{clipboard, program, Color}; use iced::{Alignment, Element}; use iced::{Command, Font}; @@ -46,6 +47,10 @@ pub struct HarborWallet { transfer_amount_str: String, send_status: SendStatus, send_failure_reason: Option, + receive_failure_reason: Option, + receive_status: ReceiveStatus, + receive_amount_str: String, + receive_invoice: Option, } impl Default for HarborWallet { @@ -54,21 +59,19 @@ impl Default for HarborWallet { } } -#[derive(Default, PartialEq, Debug, Clone, Copy)] -pub enum Route { +#[derive(Default, Debug, Clone)] +enum SendStatus { #[default] - Home, - Mints, - Transfer, - History, - Settings, + Idle, + Sending, } #[derive(Default, Debug, Clone)] -enum SendStatus { +enum ReceiveStatus { #[default] Idle, - Sending, + Generating, + WaitingToReceive, } #[derive(Debug, Clone)] @@ -78,10 +81,13 @@ pub enum Message { // Local state changes Navigate(Route), TransferAmountChanged(String), + ReceiveAmountChanged(String), + CopyToClipboard(String), // Async commands we fire from the UI to core Noop, Send(u64), Receive(u64), + GenerateInvoice, // Core messages we get from core CoreMessage(CoreUIMsg), } @@ -93,8 +99,12 @@ impl HarborWallet { balance: Amount::ZERO, active_route: Route::Home, transfer_amount_str: String::new(), + receive_amount_str: String::new(), send_status: SendStatus::Idle, send_failure_reason: None, + receive_failure_reason: None, + receive_status: ReceiveStatus::Idle, + receive_invoice: None, } } @@ -145,6 +155,10 @@ impl HarborWallet { self.transfer_amount_str = amount; Command::none() } + Message::ReceiveAmountChanged(amount) => { + self.receive_amount_str = amount; + Command::none() + } // Async commands we fire from the UI to core Message::Noop => Command::none(), Message::Send(_amount) => match self.send_status { @@ -169,6 +183,27 @@ impl HarborWallet { }) } }, + Message::GenerateInvoice => match self.receive_status { + ReceiveStatus::Generating => Command::none(), + _ => { + self.receive_failure_reason = None; + match self.receive_amount_str.parse::() { + Ok(amount) => Command::perform( + Self::async_receive(self.ui_handle.clone(), amount), + |_| Message::Noop, + ), + Err(e) => { + self.receive_amount_str = String::new(); + eprintln!("Error parsing amount: {e}"); + Command::none() + } + } + } + }, + Message::CopyToClipboard(s) => { + println!("Copying to clipboard: {s}"); + clipboard::write(s) + } // Handle any messages we get from core Message::CoreMessage(msg) => match msg { CoreUIMsg::Sending => { @@ -195,6 +230,16 @@ impl HarborWallet { self.balance = balance; Command::none() } + CoreUIMsg::ReceiveInvoiceGenerating => { + self.receive_status = ReceiveStatus::Generating; + Command::none() + } + CoreUIMsg::ReceiveInvoiceGenerated(invoice) => { + self.receive_status = ReceiveStatus::WaitingToReceive; + println!("Received invoice: {invoice}"); + self.receive_invoice = Some(invoice); + Command::none() + } }, } } @@ -202,15 +247,12 @@ impl HarborWallet { fn view(&self) -> Element { let sidebar = crate::components::sidebar(self); - let home_content = crate::routes::home(self); - let mints_content = crate::routes::mints(self); - let transfer_content = crate::routes::transfer(self); - let active_route = match self.active_route { - Route::Home => home_content, - Route::Mints => mints_content, - Route::Transfer => transfer_content, - _ => home_content, + Route::Home => crate::routes::home(self), + Route::Mints => crate::routes::mints(self), + Route::Transfer => crate::routes::transfer(self), + Route::Receive => crate::routes::receive(self), + _ => crate::routes::home(self), }; row![sidebar, active_route] diff --git a/src/routes/home.rs b/src/routes/home.rs index cbdd184..b2bfcda 100644 --- a/src/routes/home.rs +++ b/src/routes/home.rs @@ -5,10 +5,14 @@ use iced::{Alignment, Element}; use crate::{HarborWallet, Message}; +use super::Route; + pub fn home(harbor: &HarborWallet) -> Element { let balance = text(format!("{} sats", harbor.balance.sats_round_down())).size(64); let send_button = h_button("Send", SvgIcon::UpRight).on_press(Message::Send(100)); - let receive_button = h_button("Receive", SvgIcon::DownLeft).on_press(Message::Receive(100)); + // let receive_button = h_button("Receive", SvgIcon::DownLeft).on_press(Message::Receive(100)); + let receive_button = + h_button("Receive", SvgIcon::DownLeft).on_press(Message::Navigate(Route::Receive)); let buttons = row![send_button, receive_button].spacing(32); let failure_message = harbor diff --git a/src/routes/mod.rs b/src/routes/mod.rs index a103c35..c8bc64b 100644 --- a/src/routes/mod.rs +++ b/src/routes/mod.rs @@ -6,3 +6,17 @@ pub use mints::*; pub mod transfer; pub use transfer::*; + +pub mod receive; +pub use receive::*; + +#[derive(Default, PartialEq, Debug, Clone, Copy)] +pub enum Route { + #[default] + Home, + Mints, + Transfer, + History, + Settings, + Receive, +} diff --git a/src/routes/receive.rs b/src/routes/receive.rs new file mode 100644 index 0000000..5c8117d --- /dev/null +++ b/src/routes/receive.rs @@ -0,0 +1,34 @@ +use iced::widget::{column, container, scrollable, text, text_input}; +use iced::Length; +use iced::{Alignment, Element}; + +use crate::components::{h_button, SvgIcon}; +use crate::{HarborWallet, Message}; + +pub fn receive(harbor: &HarborWallet) -> Element { + let col = if let Some(invoice) = harbor.receive_invoice.as_ref() { + column![ + "Here's an invoice!", + text(format!("{invoice}")).size(16), + h_button("Copy to clipboard", SvgIcon::Copy) + .on_press(Message::CopyToClipboard(invoice.to_string())), + ] + } else { + column![ + "How much do you want to receive?", + text_input("how much?", &harbor.receive_amount_str) + .on_input(Message::ReceiveAmountChanged), + h_button("Generate Invoice", SvgIcon::DownLeft).on_press(Message::GenerateInvoice), + ] + }; + + container( + scrollable( + col.spacing(32) + .align_items(Alignment::Center) + .width(Length::Fill), + ) + .height(Length::Fill), + ) + .into() +}