From 7ca0178f934046bfbfe2d8ccde3d86009adef9ae Mon Sep 17 00:00:00 2001 From: Paul Miller Date: Tue, 14 May 2024 13:44:00 -0500 Subject: [PATCH] receive screen layout, qr code --- Cargo.lock | 68 +++++++++++++++++++++++++++++++++++++ Cargo.toml | 2 +- src/components/button.rs | 2 +- src/components/header.rs | 19 +++++++++++ src/components/mod.rs | 3 ++ src/main.rs | 10 ++++++ src/routes/home.rs | 3 +- src/routes/receive.rs | 72 ++++++++++++++++++++++++++++------------ 8 files changed, 155 insertions(+), 24 deletions(-) create mode 100644 src/components/header.rs diff --git a/Cargo.lock b/Cargo.lock index 71caeab..dbe8ebd 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1980,6 +1980,12 @@ version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "98de4bbd547a563b716d8dfa9aad1cb19bfab00f4fa09a6a4ed21dbcf44ce9c4" +[[package]] +name = "float_next_after" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8bf7cc16383c4b8d58b9905a8509f02926ce3058053c056376248d958c9df1e8" + [[package]] name = "fnv" version = "1.0.7" @@ -2817,6 +2823,7 @@ dependencies = [ "iced_core", "iced_futures", "log", + "lyon_path", "once_cell", "raw-window-handle", "rustc-hash", @@ -2877,6 +2884,7 @@ dependencies = [ "guillotiere", "iced_graphics", "log", + "lyon", "once_cell", "resvg", "rustc-hash", @@ -2892,6 +2900,7 @@ dependencies = [ "iced_renderer", "iced_runtime", "num-traits", + "qrcode", "rustc-hash", "thiserror", "unicode-segmentation", @@ -3411,6 +3420,58 @@ dependencies = [ "hashbrown 0.14.5", ] +[[package]] +name = "lyon" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91e7f9cda98b5430809e63ca5197b06c7d191bf7e26dfc467d5a3f0290e2a74f" +dependencies = [ + "lyon_algorithms", + "lyon_tessellation", +] + +[[package]] +name = "lyon_algorithms" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a3bca95f9a4955b3e4a821fbbcd5edfbd9be2a9a50bb5758173e5358bfb4c623" +dependencies = [ + "lyon_path", + "num-traits", +] + +[[package]] +name = "lyon_geom" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "edecfb8d234a2b0be031ab02ebcdd9f3b9ee418fb35e265f7a540a48d197bff9" +dependencies = [ + "arrayvec", + "euclid", + "num-traits", +] + +[[package]] +name = "lyon_path" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca507745ba7ccbc76e5c44e7b63b1a29d2b0d6126f375806a5bbaf657c7d6c45" +dependencies = [ + "lyon_geom", + "num-traits", +] + +[[package]] +name = "lyon_tessellation" +version = "1.0.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4470bd0b1f29eda66068ab1fd47719facda0a136b829bcca69287ed0ac40a134" +dependencies = [ + "float_next_after", + "lyon_path", + "num-traits", +] + [[package]] name = "macro_rules_attribute" version = "0.2.0" @@ -3672,6 +3733,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" dependencies = [ "autocfg", + "libm", ] [[package]] @@ -4285,6 +4347,12 @@ version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "43d84d1d7a6ac92673717f9f6d1518374ef257669c24ebc5ac25d5033828be58" +[[package]] +name = "qrcode" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "166f136dfdb199f98186f3649cf7a0536534a61417a1a30221b492b4fb60ce3f" + [[package]] name = "quick-xml" version = "0.31.0" diff --git a/Cargo.toml b/Cargo.toml index b4a4e16..3e67dcc 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,7 +11,7 @@ vendored = ["rusqlite/bundled-sqlcipher-vendored-openssl"] anyhow = "1" log = "0.4" pretty_env_logger = "0.5" # todo swap to a file logger -iced = { git = "https://github.com/iced-rs/iced", rev = "b30d34f", features = ["debug", "tokio", "svg"] } +iced = { git = "https://github.com/iced-rs/iced", rev = "b30d34f", features = ["debug", "tokio", "svg", "qr_code"] } tokio = { version = "1", features = ["full"] } palette = "0.7" config = "0.14.0" diff --git a/src/components/button.rs b/src/components/button.rs index 343efef..82365fa 100644 --- a/src/components/button.rs +++ b/src/components/button.rs @@ -44,7 +44,7 @@ pub fn h_button(text_str: &str, icon: SvgIcon) -> Button<'_, Message, Theme> { shadow: Shadow::default(), } }) - .width(Length::Fixed(192.)) + .width(Length::Fill) .height(Length::Fixed(64.)) } diff --git a/src/components/header.rs b/src/components/header.rs new file mode 100644 index 0000000..0f58268 --- /dev/null +++ b/src/components/header.rs @@ -0,0 +1,19 @@ +use iced::widget::text::Style; +use iced::widget::{column, text}; +use iced::{Element, Theme}; + +use crate::Message; + +use super::lighten; + +pub fn h_header(title: &'static str, subtitle: &'static str) -> Element<'static, Message> { + column![ + text(title).size(32), + text(subtitle).size(18).style(|theme: &Theme| { + let gray = lighten(theme.palette().background, 0.5); + Style { color: Some(gray) } + }) + ] + .spacing(8) + .into() +} diff --git a/src/components/mod.rs b/src/components/mod.rs index 180fc10..0ad3774 100644 --- a/src/components/mod.rs +++ b/src/components/mod.rs @@ -12,3 +12,6 @@ pub use icon::*; mod input; pub use input::*; + +mod header; +pub use header::*; diff --git a/src/main.rs b/src/main.rs index e094073..74c9f55 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,6 +1,7 @@ use core::run_core; use fedimint_core::Amount; use fedimint_ln_common::lightning_invoice::Bolt11Invoice; +use iced::widget::qr_code::Data; use routes::Route; use std::str::FromStr; use std::sync::Arc; @@ -60,6 +61,7 @@ pub struct HarborWallet { receive_status: ReceiveStatus, receive_amount_str: String, receive_invoice: Option, + receive_qr_data: Option, } impl Default for HarborWallet { @@ -131,6 +133,7 @@ impl HarborWallet { receive_failure_reason: None, receive_status: ReceiveStatus::Idle, receive_invoice: None, + receive_qr_data: None, } } @@ -300,6 +303,13 @@ impl HarborWallet { CoreUIMsg::ReceiveInvoiceGenerated(invoice) => { self.receive_status = ReceiveStatus::WaitingToReceive; println!("Received invoice: {invoice}"); + self.receive_qr_data = Some( + Data::with_error_correction( + format!("lightning:{invoice}"), + iced::widget::qr_code::ErrorCorrection::Low, + ) + .unwrap(), + ); self.receive_invoice = Some(invoice); Command::none() } diff --git a/src/routes/home.rs b/src/routes/home.rs index ca62a29..d35877a 100644 --- a/src/routes/home.rs +++ b/src/routes/home.rs @@ -17,7 +17,8 @@ pub fn home(harbor: &HarborWallet) -> Element { container(center( column![balance, buttons] .spacing(32) - .align_items(Alignment::Center), + .align_items(Alignment::Center) + .max_width(512), )) .into() } diff --git a/src/routes/receive.rs b/src/routes/receive.rs index 5c8117d..7c26134 100644 --- a/src/routes/receive.rs +++ b/src/routes/receive.rs @@ -1,34 +1,64 @@ -use iced::widget::{column, container, scrollable, text, text_input}; -use iced::Length; -use iced::{Alignment, Element}; +use iced::widget::container::Style; +use iced::widget::{column, container, qr_code, scrollable, text}; +use iced::{Border, Element, Padding}; +use iced::{Color, Length}; -use crate::components::{h_button, SvgIcon}; +use crate::components::{h_button, h_header, h_input, SvgIcon}; use crate::{HarborWallet, Message}; pub fn receive(harbor: &HarborWallet) -> Element { - let col = if let Some(invoice) = harbor.receive_invoice.as_ref() { + let column = if let Some(invoice) = harbor.receive_invoice.as_ref() { + let header = h_header("Receive", "Scan this QR or copy the string."); + + let data = harbor.receive_qr_data.as_ref().unwrap(); + let qr_code = qr_code(data).style(|_theme| iced::widget::qr_code::Style { + background: Color::WHITE, + cell: Color::BLACK, + }); + let qr_container = container(qr_code).padding(16).style(|_theme| Style { + background: Some(iced::Background::Color(Color::WHITE)), + border: Border { + radius: (8.).into(), + ..Border::default() + }, + ..Style::default() + }); + + let first_ten_chars = invoice.to_string().chars().take(10).collect::(); + column![ - "Here's an invoice!", - text(format!("{invoice}")).size(16), + header, + qr_container, + text(format!("{first_ten_chars}...")).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), - ] + let header = h_header("Receive", "Receive on-chain or via lightning."); + + let amount_input = h_input( + "Amount", + "420", + &harbor.receive_amount_str, + Message::ReceiveAmountChanged, + Message::Noop, + false, + None, + Some("sats"), + ); + + let generate_button = + h_button("Generate Invoice", SvgIcon::DownLeft).on_press(Message::GenerateInvoice); + + column![header, amount_input, generate_button] }; - container( - scrollable( - col.spacing(32) - .align_items(Alignment::Center) - .width(Length::Fill), - ) - .height(Length::Fill), - ) + container(scrollable( + column + .spacing(48) + .width(Length::Fill) + .max_width(512) + .padding(Padding::new(48.)), + )) .into() }