diff --git a/assets/icons/restart.svg b/assets/icons/restart.svg
new file mode 100644
index 0000000..a01bcae
--- /dev/null
+++ b/assets/icons/restart.svg
@@ -0,0 +1,4 @@
+
diff --git a/src/components/button.rs b/src/components/button.rs
index 53b1dac..d14c7bc 100644
--- a/src/components/button.rs
+++ b/src/components/button.rs
@@ -1,7 +1,7 @@
use iced::{
widget::{
button::{self, Status},
- center, horizontal_space, row, text, Button, Svg,
+ center, horizontal_space, row, text, Button,
},
Border, Color, Element, Length, Shadow, Theme,
};
@@ -12,16 +12,13 @@ use super::{darken, lighten, map_icon, the_spinner, SvgIcon};
pub fn h_button(text_str: &str, icon: SvgIcon, loading: bool) -> Button<'_, Message, Theme> {
let spinner: Element<'static, Message, Theme> = the_spinner();
- let svg: Svg<'_, Theme> = map_icon(icon);
+ let svg = map_icon(icon, 24., 24.);
let content = if loading {
row![spinner].align_items(iced::Alignment::Center)
} else {
- row![
- svg.width(Length::Fixed(24.)).height(Length::Fixed(24.)),
- text(text_str).size(24.)
- ]
- .align_items(iced::Alignment::Center)
- .spacing(16)
+ row![svg, text(text_str).size(24.)]
+ .align_items(iced::Alignment::Center)
+ .spacing(16)
};
Button::new(center(content))
@@ -62,22 +59,11 @@ pub fn sidebar_button(
active_route: Route,
) -> Button<'_, Message, Theme> {
let is_active = self_route == active_route;
- let svg: Svg<'_, Theme> = map_icon(icon);
- let content = row!(
- svg.width(Length::Fixed(24.)).height(Length::Fixed(24.)),
- text(text_str).size(24.),
- horizontal_space(),
- // .font(Font {
-
- // family: iced::font::Family::default(),
- // weight: iced::font::Weight::Bold,
- // stretch: iced::font::Stretch::Normal,
- // style: iced::font::Style::Normal,
- // })
- )
- .align_items(iced::Alignment::Center)
- .spacing(16)
- .padding(16);
+ let svg = map_icon(icon, 24., 24.);
+ let content = row!(svg, text(text_str).size(24.), horizontal_space(),)
+ .align_items(iced::Alignment::Center)
+ .spacing(16)
+ .padding(16);
Button::new(content)
.style(move |theme, status| {
diff --git a/src/components/icon.rs b/src/components/icon.rs
index 3960839..f6a6467 100644
--- a/src/components/icon.rs
+++ b/src/components/icon.rs
@@ -1,4 +1,6 @@
-use iced::{widget::Svg, Theme};
+use iced::{widget::Svg, Element, Theme};
+
+use crate::Message;
pub enum SvgIcon {
ChevronDown,
@@ -13,9 +15,10 @@ pub enum SvgIcon {
Copy,
Plus,
Qr,
+ Restart,
}
-pub fn map_icon(icon: SvgIcon) -> Svg<'static, Theme> {
+pub fn map_icon(icon: SvgIcon, width: f32, height: f32) -> Element<'static, Message, Theme> {
match icon {
SvgIcon::ChevronDown => Svg::from_path("assets/icons/chevron_down.svg"),
SvgIcon::DownLeft => Svg::from_path("assets/icons/down_left.svg"),
@@ -29,5 +32,9 @@ pub fn map_icon(icon: SvgIcon) -> Svg<'static, Theme> {
SvgIcon::Copy => Svg::from_path("assets/icons/copy.svg"),
SvgIcon::Plus => Svg::from_path("assets/icons/plus.svg"),
SvgIcon::Qr => Svg::from_path("assets/icons/qr.svg"),
+ SvgIcon::Restart => Svg::from_path("assets/icons/restart.svg"),
}
+ .width(width)
+ .height(height)
+ .into()
}
diff --git a/src/components/layout.rs b/src/components/layout.rs
index 54c25de..45a5ec4 100644
--- a/src/components/layout.rs
+++ b/src/components/layout.rs
@@ -1,4 +1,4 @@
-use iced::widget::{container, scrollable, Column};
+use iced::widget::{container, horizontal_space, row, scrollable, Column};
use iced::Length;
use iced::{Element, Padding};
@@ -6,8 +6,14 @@ use crate::Message;
pub fn basic_layout(column: Column) -> Element {
container(
- scrollable(column.width(Length::Fixed(512.)).padding(Padding::new(48.)))
- .height(Length::Fill),
+ scrollable(row![
+ column
+ .width(Length::Fixed(512.))
+ .padding(Padding::new(48.))
+ .max_width(512),
+ horizontal_space(),
+ ])
+ .height(Length::Fill),
)
.into()
}
diff --git a/src/components/mini_copy.rs b/src/components/mini_copy.rs
new file mode 100644
index 0000000..6d9590f
--- /dev/null
+++ b/src/components/mini_copy.rs
@@ -0,0 +1,39 @@
+use iced::{
+ widget::{
+ button::{self, Status}, Button,
+ },
+ Border, Color, Length, Shadow, Theme,
+};
+
+use crate::Message;
+
+use super::{darken, lighten, map_icon, SvgIcon};
+
+pub fn mini_copy(text: String) -> Button<'static, Message, Theme> {
+ let icon = map_icon(SvgIcon::Copy, 24., 24.);
+
+ Button::new(icon)
+ .on_press(Message::CopyToClipboard(text.to_string()))
+ .style(|theme: &Theme, status| {
+ let border = Border {
+ color: Color::WHITE,
+ width: 0.,
+ radius: (8.).into(),
+ };
+
+ let background = match status {
+ Status::Hovered => lighten(theme.palette().background, 0.1),
+ Status::Pressed => darken(Color::BLACK, 0.1),
+ _ => theme.palette().background,
+ };
+ button::Style {
+ background: Some(background.into()),
+ text_color: Color::WHITE,
+ border,
+ shadow: Shadow::default(),
+ }
+ })
+ .padding(6)
+ .width(Length::Fixed(32.))
+ .height(Length::Fixed(32.))
+}
diff --git a/src/components/mod.rs b/src/components/mod.rs
index 167aa16..bdf9004 100644
--- a/src/components/mod.rs
+++ b/src/components/mod.rs
@@ -39,3 +39,6 @@ pub use rules::*;
mod layout;
pub use layout::*;
+
+mod mini_copy;
+pub use mini_copy::*;
diff --git a/src/components/screen_header.rs b/src/components/screen_header.rs
index e8eb19f..7290b7a 100644
--- a/src/components/screen_header.rs
+++ b/src/components/screen_header.rs
@@ -10,7 +10,7 @@ use super::{hr, map_icon, vr, FederationItem, SvgIcon};
pub fn h_screen_header(harbor: &HarborWallet, show_balance: bool) -> Element {
if let Some(item) = harbor.federation_list.first() {
let FederationItem { name, id: _id } = item;
- let people_icon = map_icon(SvgIcon::People).width(24).height(24);
+ let people_icon = map_icon(SvgIcon::People, 24., 24.);
let current_federation = row![people_icon, text(name).size(24)]
.align_items(Alignment::Center)
.spacing(16)
diff --git a/src/routes/receive.rs b/src/routes/receive.rs
index 90d6981..26e9be8 100644
--- a/src/routes/receive.rs
+++ b/src/routes/receive.rs
@@ -1,120 +1,161 @@
use iced::widget::container::Style;
-use iced::widget::{column, container, qr_code, radio, text};
+use iced::widget::{column, container, qr_code, radio, row, text};
use iced::Color;
use iced::{Border, Element, Font};
+use crate::bridge::ReceiveSuccessMsg;
use crate::components::{
- basic_layout, h_button, h_caption_text, h_header, h_input, h_screen_header, SvgIcon,
+ basic_layout, h_button, h_caption_text, h_header, h_input, h_screen_header, mini_copy, SvgIcon,
};
use crate::{HarborWallet, Message, ReceiveMethod, ReceiveStatus};
pub fn receive(harbor: &HarborWallet) -> Element {
- let lightning_choice = radio(
- "Lightning",
- ReceiveMethod::Lightning,
- Some(harbor.receive_method),
- Message::ReceiveMethodChanged,
- )
- .text_size(18);
-
- let lightning_caption = h_caption_text("Good for small amounts. Instant settlement, low fees.");
-
- let lightning = column![lightning_choice, lightning_caption,].spacing(8);
-
- let onchain_choice = radio(
- "On-chain",
- ReceiveMethod::OnChain,
- Some(harbor.receive_method),
- Message::ReceiveMethodChanged,
- )
- .text_size(18);
-
- let onchain_caption = h_caption_text(
- "Good for large amounts. Requires on-chain fees and 10 block confirmations.",
- );
-
- let onchain = column![onchain_choice, onchain_caption,].spacing(8);
-
- let method_choice_label = text("Receive method").size(24);
-
- let method_choice = column![method_choice_label, lightning, onchain].spacing(16);
-
let receive_string = harbor
.receive_invoice
.as_ref()
.map(|i| i.to_string())
.or_else(|| harbor.receive_address.as_ref().map(|a| a.to_string()));
- let success_message = harbor.receive_success_msg.as_ref().map(|r| {
- text(format!("Success: {r:?}"))
- .size(32)
- .color(Color::from_rgb(0., 255., 0.))
- });
-
- let column = if let Some(success_message) = success_message {
- let header = h_header("Success!", "You did a good job!");
- let reset_button =
- h_button("Start over", SvgIcon::Squirrel, false).on_press(Message::ReceiveStateReset);
- column![header, success_message, reset_button]
- } else if let Some(string) = receive_string {
- 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_20_chars = string.chars().take(20).collect::();
-
- column![
- header,
- qr_container,
- text(format!("{first_20_chars}...")).size(16).font(Font {
- family: iced::font::Family::Monospace,
- weight: iced::font::Weight::Normal,
- stretch: iced::font::Stretch::Normal,
- style: iced::font::Style::Normal,
- }),
- h_button("Copy to clipboard", SvgIcon::Copy, false)
- .on_press(Message::CopyToClipboard(string)),
- h_button("Start over", SvgIcon::Squirrel, false).on_press(Message::ReceiveStateReset),
- ]
- } else {
- let header = h_header("Receive", "Receive on-chain or via lightning.");
-
- let amount_input = h_input(
- "Amount",
- "420",
- &harbor.receive_amount_str,
- Message::ReceiveAmountChanged,
- None,
- false,
- None,
- Some("sats"),
- );
-
- let generating = harbor.receive_status == ReceiveStatus::Generating;
-
- let generate_button = h_button("Generate Invoice", SvgIcon::Qr, generating)
- .on_press(Message::GenerateInvoice);
-
- let generate_address_button = h_button("Generate Address", SvgIcon::Qr, generating)
- .on_press(Message::GenerateAddress);
-
- match harbor.receive_method {
- ReceiveMethod::Lightning => {
- column![header, method_choice, amount_input, generate_button]
+ let reset_button =
+ h_button("Start over", SvgIcon::Restart, false).on_press(Message::ReceiveStateReset);
+
+ let bold_font = Font {
+ family: iced::font::Family::Monospace,
+ weight: iced::font::Weight::Bold,
+ stretch: iced::font::Stretch::Normal,
+ style: iced::font::Style::Normal,
+ };
+
+ let mono_font = Font {
+ family: iced::font::Family::Monospace,
+ weight: iced::font::Weight::Normal,
+ stretch: iced::font::Stretch::Normal,
+ style: iced::font::Style::Normal,
+ };
+
+ let column = match (&harbor.receive_success_msg, receive_string) {
+ // Starting state
+ (None, None) => {
+ let header = h_header("Receive", "Receive on-chain or via lightning.");
+
+ let lightning_choice = radio(
+ "Lightning",
+ ReceiveMethod::Lightning,
+ Some(harbor.receive_method),
+ Message::ReceiveMethodChanged,
+ )
+ .text_size(18);
+ let lightning_caption =
+ h_caption_text("Good for small amounts. Instant settlement, low fees.");
+ let lightning = column![lightning_choice, lightning_caption,].spacing(8);
+
+ let onchain_choice = radio(
+ "On-chain",
+ ReceiveMethod::OnChain,
+ Some(harbor.receive_method),
+ Message::ReceiveMethodChanged,
+ )
+ .text_size(18);
+ let onchain_caption = h_caption_text(
+ "Good for large amounts. Requires on-chain fees and 10 block confirmations.",
+ );
+ let onchain = column![onchain_choice, onchain_caption,].spacing(8);
+
+ let method_choice_label = text("Receive method").size(24);
+ let method_choice = column![method_choice_label, lightning, onchain].spacing(16);
+
+ let amount_input = h_input(
+ "Amount",
+ "420",
+ &harbor.receive_amount_str,
+ Message::ReceiveAmountChanged,
+ None,
+ false,
+ None,
+ Some("sats"),
+ );
+
+ let generating = harbor.receive_status == ReceiveStatus::Generating;
+
+ let generate_button = h_button("Generate Invoice", SvgIcon::Qr, generating)
+ .on_press(Message::GenerateInvoice);
+
+ let generate_address_button = h_button("Generate Address", SvgIcon::Qr, generating)
+ .on_press(Message::GenerateAddress);
+
+ match harbor.receive_method {
+ ReceiveMethod::Lightning => {
+ column![header, method_choice, amount_input, generate_button]
+ }
+ ReceiveMethod::OnChain => column![header, method_choice, generate_address_button],
}
- ReceiveMethod::OnChain => column![header, method_choice, generate_address_button],
+ }
+ // We've generated an invoice or address
+ (None, Some(receive_string)) => {
+ 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_20_chars = receive_string.chars().take(20).collect::();
+
+ column![
+ header,
+ qr_container,
+ text(format!("{first_20_chars}...")).size(16).font(Font {
+ family: iced::font::Family::Monospace,
+ weight: iced::font::Weight::Normal,
+ stretch: iced::font::Stretch::Normal,
+ style: iced::font::Style::Normal,
+ }),
+ h_button("Copy to clipboard", SvgIcon::Copy, false)
+ .on_press(Message::CopyToClipboard(receive_string)),
+ reset_button
+ ]
+ }
+ // Success states
+ (Some(ReceiveSuccessMsg::Lightning), _) => {
+ let header = h_header("Got it", "Payment received");
+
+ // TODO: should have some info here we can show like amount, fee, etc.
+
+ column![header, reset_button]
+ }
+ (Some(ReceiveSuccessMsg::Onchain { txid }), _) => {
+ let txid_str = txid.to_string();
+ let header = h_header("Got it", "Payment received");
+
+ let txid_str_shortened = if txid_str.len() > 20 {
+ // get the first 10 and last 10 chars
+ let txid_str_start = &txid_str[0..10];
+ let txid_str_end = &txid_str[txid_str.len() - 10..];
+
+ // add ellipsis
+ format!("{txid_str_start}...{txid_str_end}")
+ } else {
+ txid_str.clone()
+ };
+
+ let txid = row![
+ text("txid").font(bold_font),
+ text(txid_str_shortened).font(mono_font),
+ mini_copy(txid_str)
+ ]
+ .align_items(iced::Alignment::Center)
+ .spacing(8);
+
+ column![header, txid, reset_button]
}
};