diff --git a/harbor-ui/assets/icons/arrow_left.svg b/harbor-ui/assets/icons/arrow_left.svg
new file mode 100644
index 0000000..2ff42f0
--- /dev/null
+++ b/harbor-ui/assets/icons/arrow_left.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/harbor-ui/assets/icons/clock.svg b/harbor-ui/assets/icons/clock.svg
new file mode 100644
index 0000000..8691d16
--- /dev/null
+++ b/harbor-ui/assets/icons/clock.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/harbor-ui/assets/icons/eye_closed.svg b/harbor-ui/assets/icons/eye_closed.svg
new file mode 100644
index 0000000..6f099a0
--- /dev/null
+++ b/harbor-ui/assets/icons/eye_closed.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/harbor-ui/src/components/button.rs b/harbor-ui/src/components/button.rs
index 5393878..5684c05 100644
--- a/harbor-ui/src/components/button.rs
+++ b/harbor-ui/src/components/button.rs
@@ -41,9 +41,15 @@ pub fn h_button(text_str: &str, icon: SvgIcon, loading: bool) -> Button<'_, Mess
Status::Pressed => darken(Color::BLACK, 0.1),
_ => theme.palette().background,
};
+
+ let text_color = match status {
+ Status::Disabled => gray,
+ _ => Color::WHITE,
+ };
+
button::Style {
background: Some(background.into()),
- text_color: Color::WHITE,
+ text_color,
border,
shadow: Shadow::default(),
}
@@ -67,6 +73,8 @@ pub fn sidebar_button(
Button::new(content)
.style(move |theme, status| {
+ let gray = lighten(theme.palette().background, 0.5);
+
let border = Border {
color: Color::WHITE,
width: 0.,
@@ -85,9 +93,15 @@ pub fn sidebar_button(
(Status::Pressed, false) => darken(bg_color, 0.1),
_ => bg_color,
};
+
+ let text_color = match status {
+ Status::Disabled => gray,
+ _ => Color::WHITE,
+ };
+
button::Style {
background: Some(background.into()),
- text_color: Color::WHITE,
+ text_color,
border,
shadow: Shadow::default(),
}
diff --git a/harbor-ui/src/components/federation_item.rs b/harbor-ui/src/components/federation_item.rs
index 6bd90c9..2122866 100644
--- a/harbor-ui/src/components/federation_item.rs
+++ b/harbor-ui/src/components/federation_item.rs
@@ -1,9 +1,9 @@
+use crate::Message;
+use harbor_client::db_models::FederationItem;
use iced::{
widget::{column, row, text},
Alignment, Element,
};
-use harbor_client::db_models::FederationItem;
-use crate::Message;
use super::{bold_text, h_button, regular_text, subtitle, truncate_text};
use super::{format_amount, map_icon, SvgIcon};
diff --git a/harbor-ui/src/components/icon.rs b/harbor-ui/src/components/icon.rs
index 2b1cac8..e6175fc 100644
--- a/harbor-ui/src/components/icon.rs
+++ b/harbor-ui/src/components/icon.rs
@@ -9,6 +9,7 @@ pub enum SvgIcon {
Heart,
Home,
LeftRight,
+ ArrowLeft,
People,
Settings,
Squirrel,
@@ -21,6 +22,8 @@ pub enum SvgIcon {
Bolt,
Chain,
Eye,
+ EyeClosed,
+ Clock,
}
macro_rules! icon_handle {
@@ -51,6 +54,9 @@ pub fn map_icon<'a>(icon: SvgIcon, width: f32, height: f32) -> Svg<'a, Theme> {
SvgIcon::Bolt => icon_handle!("bolt.svg"),
SvgIcon::Chain => icon_handle!("chain.svg"),
SvgIcon::Eye => icon_handle!("eye.svg"),
+ SvgIcon::EyeClosed => icon_handle!("eye_closed.svg"),
+ SvgIcon::Clock => icon_handle!("clock.svg"),
+ SvgIcon::ArrowLeft => icon_handle!("arrow_left.svg"),
}
.width(width)
.height(height)
diff --git a/harbor-ui/src/components/indicator.rs b/harbor-ui/src/components/indicator.rs
new file mode 100644
index 0000000..bb9e76a
--- /dev/null
+++ b/harbor-ui/src/components/indicator.rs
@@ -0,0 +1,369 @@
+use iced::advanced::{
+ layout::{self, Layout},
+ overlay::{self, Group},
+ renderer,
+ widget::{Tree, Widget},
+ Clipboard, Overlay, Shell,
+};
+use iced::widget::container;
+use iced::{Element, Event, Length, Point, Rectangle, Size, Vector};
+
+/// An element to display a widget over another, controlled by a boolean flag.
+///
+/// # Example
+/// ```ignore
+/// use crate::components::indicator;
+///
+/// indicator(
+/// "Main content",
+/// "Indicator content",
+/// indicator::Position::Top,
+/// true, // show
+/// )
+/// ```
+#[allow(missing_debug_implementations)]
+pub struct Indicator<'a, Message, Theme = iced::Theme, Renderer = iced::Renderer>
+where
+ Theme: container::Catalog,
+ Renderer: iced::advanced::text::Renderer,
+{
+ content: Element<'a, Message, Theme, Renderer>,
+ indicator: Element<'a, Message, Theme, Renderer>,
+ position: Position,
+ show: bool,
+ gap: f32,
+ padding: f32,
+ snap_within_viewport: bool,
+ class: Theme::Class<'a>,
+}
+
+impl<'a, Message, Theme, Renderer> Indicator<'a, Message, Theme, Renderer>
+where
+ Theme: container::Catalog,
+ Renderer: iced::advanced::text::Renderer,
+{
+ /// The default padding of an [`Indicator`].
+ const DEFAULT_PADDING: f32 = 5.0;
+
+ /// Creates a new [`Indicator`].
+ pub fn new(
+ content: impl Into>,
+ indicator: impl Into>,
+ position: Position,
+ show: bool,
+ ) -> Self {
+ Indicator {
+ content: content.into(),
+ indicator: indicator.into(),
+ position,
+ show,
+ gap: 0.0,
+ padding: Self::DEFAULT_PADDING,
+ snap_within_viewport: true,
+ class: Theme::default(),
+ }
+ }
+
+ /// Sets the gap between the content and its [`Indicator`].
+ pub fn gap(mut self, gap: impl Into) -> Self {
+ self.gap = gap.into();
+ self
+ }
+
+ /// Sets the padding of the [`Indicator`].
+ pub fn padding(mut self, padding: impl Into) -> Self {
+ self.padding = padding.into();
+ self
+ }
+
+ /// Sets whether the [`Indicator`] is snapped within the viewport.
+ pub fn snap_within_viewport(mut self, snap: bool) -> Self {
+ self.snap_within_viewport = snap;
+ self
+ }
+
+ /// Sets the style of the [`Indicator`].
+ #[must_use]
+ pub fn style(mut self, style: impl Fn(&Theme) -> container::Style + 'a) -> Self
+ where
+ Theme::Class<'a>: From>,
+ {
+ self.class = (Box::new(style) as container::StyleFn<'a, Theme>).into();
+ self
+ }
+}
+
+/// The position of the indicator.
+#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
+pub enum Position {
+ /// The indicator will appear on the top of the widget.
+ #[default]
+ Top,
+ /// The indicator will appear on the bottom of the widget.
+ Bottom,
+ /// The indicator will appear on the left of the widget.
+ Left,
+ /// The indicator will appear on the right of the widget.
+ Right,
+}
+
+impl Widget
+ for Indicator<'_, Message, Theme, Renderer>
+where
+ Theme: container::Catalog,
+ Renderer: iced::advanced::text::Renderer,
+{
+ fn children(&self) -> Vec {
+ vec![Tree::new(&self.content), Tree::new(&self.indicator)]
+ }
+
+ fn diff(&self, tree: &mut Tree) {
+ tree.diff_children(&[self.content.as_widget(), self.indicator.as_widget()]);
+ }
+
+ fn size(&self) -> Size {
+ self.content.as_widget().size()
+ }
+
+ fn layout(
+ &self,
+ tree: &mut Tree,
+ renderer: &Renderer,
+ limits: &layout::Limits,
+ ) -> layout::Node {
+ self.content
+ .as_widget()
+ .layout(&mut tree.children[0], renderer, limits)
+ }
+
+ fn on_event(
+ &mut self,
+ tree: &mut Tree,
+ event: Event,
+ layout: Layout<'_>,
+ cursor: iced::mouse::Cursor,
+ renderer: &Renderer,
+ clipboard: &mut dyn Clipboard,
+ shell: &mut Shell<'_, Message>,
+ viewport: &Rectangle,
+ ) -> iced::event::Status {
+ self.content.as_widget_mut().on_event(
+ &mut tree.children[0],
+ event,
+ layout,
+ cursor,
+ renderer,
+ clipboard,
+ shell,
+ viewport,
+ )
+ }
+
+ fn draw(
+ &self,
+ tree: &Tree,
+ renderer: &mut Renderer,
+ theme: &Theme,
+ style: &renderer::Style,
+ layout: Layout<'_>,
+ cursor: iced::mouse::Cursor,
+ viewport: &Rectangle,
+ ) {
+ self.content.as_widget().draw(
+ &tree.children[0],
+ renderer,
+ theme,
+ style,
+ layout,
+ cursor,
+ viewport,
+ );
+ }
+
+ fn overlay<'b>(
+ &'b mut self,
+ tree: &'b mut Tree,
+ layout: Layout<'_>,
+ renderer: &Renderer,
+ translation: Vector,
+ ) -> Option> {
+ let mut children = tree.children.iter_mut();
+
+ let content = self.content.as_widget_mut().overlay(
+ children.next().unwrap(),
+ layout,
+ renderer,
+ translation,
+ );
+
+ let indicator = if self.show {
+ let indicator_overlay = IndicatorContent {
+ position: layout.position() + translation,
+ indicator: &self.indicator,
+ state: children.next().unwrap(),
+ content_bounds: layout.bounds(),
+ snap_within_viewport: self.snap_within_viewport,
+ positioning: self.position,
+ gap: self.gap,
+ padding: self.padding,
+ class: &self.class,
+ };
+
+ Some(overlay::Element::new(Box::new(indicator_overlay)))
+ } else {
+ None
+ };
+
+ if content.is_some() || indicator.is_some() {
+ Some(Group::with_children(content.into_iter().chain(indicator).collect()).overlay())
+ } else {
+ None
+ }
+ }
+}
+
+struct IndicatorContent<'a, 'b, Message, Theme, Renderer>
+where
+ Theme: container::Catalog,
+ Renderer: iced::advanced::text::Renderer,
+{
+ position: Point,
+ indicator: &'b Element<'a, Message, Theme, Renderer>,
+ state: &'b mut Tree,
+ content_bounds: Rectangle,
+ snap_within_viewport: bool,
+ positioning: Position,
+ gap: f32,
+ padding: f32,
+ class: &'b Theme::Class<'a>,
+}
+
+impl Overlay
+ for IndicatorContent<'_, '_, Message, Theme, Renderer>
+where
+ Theme: container::Catalog,
+ Renderer: iced::advanced::text::Renderer,
+{
+ fn layout(&mut self, renderer: &Renderer, bounds: Size) -> layout::Node {
+ let viewport = Rectangle::with_size(bounds);
+
+ let indicator_layout = self.indicator.as_widget().layout(
+ self.state,
+ renderer,
+ &layout::Limits::new(
+ Size::ZERO,
+ self.snap_within_viewport
+ .then(|| viewport.size())
+ .unwrap_or(Size::INFINITY),
+ )
+ .shrink(iced::Padding::from(self.padding)),
+ );
+
+ let bounds = indicator_layout.bounds();
+ let x_center = self.position.x + (self.content_bounds.width - bounds.width) / 2.0;
+ let y_center = self.position.y + (self.content_bounds.height - bounds.height) / 2.0;
+
+ let mut indicator_bounds = {
+ let offset = match self.positioning {
+ Position::Top => Vector::new(
+ x_center,
+ self.position.y - bounds.height - self.gap - self.padding,
+ ),
+ Position::Bottom => Vector::new(
+ x_center,
+ self.position.y + self.content_bounds.height + self.gap + self.padding,
+ ),
+ Position::Left => Vector::new(
+ self.position.x - bounds.width - self.gap - self.padding,
+ y_center,
+ ),
+ Position::Right => Vector::new(
+ self.position.x + self.content_bounds.width + self.gap + self.padding,
+ y_center,
+ ),
+ };
+
+ Rectangle {
+ x: offset.x - self.padding,
+ y: offset.y - self.padding,
+ width: bounds.width + self.padding * 2.0,
+ height: bounds.height + self.padding * 2.0,
+ }
+ };
+
+ if self.snap_within_viewport {
+ if indicator_bounds.x < viewport.x {
+ indicator_bounds.x = viewport.x;
+ } else if viewport.x + viewport.width < indicator_bounds.x + indicator_bounds.width {
+ indicator_bounds.x = viewport.x + viewport.width - indicator_bounds.width;
+ }
+
+ if indicator_bounds.y < viewport.y {
+ indicator_bounds.y = viewport.y;
+ } else if viewport.y + viewport.height < indicator_bounds.y + indicator_bounds.height {
+ indicator_bounds.y = viewport.y + viewport.height - indicator_bounds.height;
+ }
+ }
+
+ layout::Node::with_children(
+ indicator_bounds.size(),
+ vec![indicator_layout.translate(Vector::new(self.padding, self.padding))],
+ )
+ .translate(Vector::new(indicator_bounds.x, indicator_bounds.y))
+ }
+
+ fn draw(
+ &self,
+ renderer: &mut Renderer,
+ theme: &Theme,
+ style: &renderer::Style,
+ layout: Layout<'_>,
+ cursor: iced::mouse::Cursor,
+ ) {
+ let container_style = theme.style(self.class);
+
+ container::draw_background(renderer, &container_style, layout.bounds());
+
+ let defaults = renderer::Style {
+ text_color: container_style.text_color.unwrap_or(style.text_color),
+ };
+
+ self.indicator.as_widget().draw(
+ self.state,
+ renderer,
+ theme,
+ &defaults,
+ layout.children().next().unwrap(),
+ cursor,
+ &Rectangle::with_size(Size::INFINITY),
+ );
+ }
+}
+
+impl<'a, Message, Theme, Renderer> From>
+ for Element<'a, Message, Theme, Renderer>
+where
+ Message: 'a,
+ Theme: container::Catalog + 'a,
+ Renderer: iced::advanced::text::Renderer + 'a,
+{
+ fn from(
+ indicator: Indicator<'a, Message, Theme, Renderer>,
+ ) -> Element<'a, Message, Theme, Renderer> {
+ Element::new(indicator)
+ }
+}
+
+/// Creates a new [`Indicator`] with the given content and indicator elements.
+pub fn indicator<'a, Message, Theme, Renderer>(
+ content: impl Into>,
+ indicator: impl Into>,
+ position: Position,
+ show: bool,
+) -> Indicator<'a, Message, Theme, Renderer>
+where
+ Theme: container::Catalog,
+ Renderer: iced::advanced::text::Renderer,
+{
+ Indicator::new(content, indicator, position, show)
+}
diff --git a/harbor-ui/src/components/input.rs b/harbor-ui/src/components/input.rs
index 9608215..bd573c2 100644
--- a/harbor-ui/src/components/input.rs
+++ b/harbor-ui/src/components/input.rs
@@ -3,7 +3,8 @@ use iced::{
column, row, text,
text_input::{self, focus, Id},
TextInput,
- }, Background, Border, Color, Element, Task, Theme
+ },
+ Background, Border, Color, Element, Task, Theme,
};
use crate::Message;
diff --git a/harbor-ui/src/components/mod.rs b/harbor-ui/src/components/mod.rs
index fa7a1a0..a685945 100644
--- a/harbor-ui/src/components/mod.rs
+++ b/harbor-ui/src/components/mod.rs
@@ -51,3 +51,6 @@ pub use colors::*;
mod styles;
pub use styles::*;
+
+mod indicator;
+pub use indicator::*;
diff --git a/harbor-ui/src/components/screen_header.rs b/harbor-ui/src/components/screen_header.rs
index 95df8b3..b9dfe3e 100644
--- a/harbor-ui/src/components/screen_header.rs
+++ b/harbor-ui/src/components/screen_header.rs
@@ -1,19 +1,15 @@
+use crate::{HarborWallet, Message};
+use harbor_client::db_models::FederationItem;
use iced::{
widget::{column, row, text},
Alignment, Element, Length,
};
-use harbor_client::db_models::FederationItem;
-use crate::{HarborWallet, Message};
use super::{format_amount, hr, map_icon, vr, SvgIcon};
pub fn h_screen_header(harbor: &HarborWallet, show_balance: bool) -> Element {
if let Some(item) = harbor.active_federation.as_ref() {
- let FederationItem {
- name,
- balance,
- ..
- } = item;
+ let FederationItem { name, balance, .. } = item;
let people_icon = map_icon(SvgIcon::People, 24., 24.);
let current_federation = row![people_icon, text(name).size(24)]
.align_y(Alignment::Center)
diff --git a/harbor-ui/src/components/sidebar.rs b/harbor-ui/src/components/sidebar.rs
index d1e6e82..e8d0832 100644
--- a/harbor-ui/src/components/sidebar.rs
+++ b/harbor-ui/src/components/sidebar.rs
@@ -1,37 +1,66 @@
+use crate::components::indicator::Position;
use crate::components::SvgIcon;
use crate::routes::MintSubroute;
use iced::widget::container::Style;
-use iced::widget::{column, container, vertical_space};
-use iced::Border;
+use iced::widget::{column, container, row, text, vertical_space};
use iced::{Alignment, Element, Shadow};
+use iced::{Border, Theme};
use crate::{HarborWallet, Message, Route};
-use super::{harbor_logo, lighten, sidebar_button};
+use super::{harbor_logo, indicator, lighten, map_icon, sidebar_button};
pub fn sidebar(harbor: &HarborWallet) -> Element {
+ let transfer_disabled = harbor.balance_sats() == 0 || harbor.federation_list.is_empty();
+ let transfer_button = sidebar_button(
+ "Transfer",
+ SvgIcon::LeftRight,
+ Route::Transfer,
+ harbor.active_route,
+ );
+ let add_a_mint_cta = container(
+ row![
+ map_icon(SvgIcon::ArrowLeft, 14., 14.),
+ text("Add a mint to get started").size(14)
+ ]
+ .spacing(8)
+ .align_y(iced::Alignment::Center),
+ )
+ .padding(8)
+ .style(|theme: &Theme| container::Style {
+ text_color: Some(theme.palette().text),
+ background: Some(theme.palette().primary.into()),
+ border: Border {
+ radius: 4.0.into(),
+ ..Default::default()
+ },
+ ..Default::default()
+ });
let sidebar = container(
column![
harbor_logo(),
sidebar_button("Home", SvgIcon::Home, Route::Home, harbor.active_route)
.on_press(Message::Navigate(Route::Home)),
- sidebar_button(
- "Mints",
- SvgIcon::People,
- Route::Mints(MintSubroute::List),
- harbor.active_route
- )
- .on_press(Message::Navigate(Route::Mints(MintSubroute::List))),
- sidebar_button(
- "Transfer",
- SvgIcon::LeftRight,
- Route::Transfer,
- harbor.active_route
- )
- .on_press(Message::Navigate(Route::Transfer)),
+ indicator(
+ sidebar_button(
+ "Mints",
+ SvgIcon::People,
+ Route::Mints(MintSubroute::List),
+ harbor.active_route
+ )
+ .on_press(Message::Navigate(Route::Mints(MintSubroute::List))),
+ add_a_mint_cta,
+ Position::Right,
+ harbor.show_add_a_mint_cta
+ ),
+ if !transfer_disabled {
+ transfer_button.on_press(Message::Navigate(Route::Transfer))
+ } else {
+ transfer_button
+ },
sidebar_button(
"History",
- SvgIcon::Squirrel,
+ SvgIcon::Clock,
Route::History,
harbor.active_route
)
diff --git a/harbor-ui/src/main.rs b/harbor-ui/src/main.rs
index 6ce8832..187ef2f 100644
--- a/harbor-ui/src/main.rs
+++ b/harbor-ui/src/main.rs
@@ -1,3 +1,5 @@
+use crate::bridge::run_core;
+use crate::components::focus_input_id;
use bitcoin::address::NetworkUnchecked;
use bitcoin::Address;
use components::{Toast, ToastManager, ToastStatus};
@@ -6,6 +8,9 @@ use fedimint_core::core::ModuleKind;
use fedimint_core::invite_code::InviteCode;
use fedimint_core::Amount;
use fedimint_ln_common::lightning_invoice::Bolt11Invoice;
+use harbor_client::db_models::transaction_item::TransactionItem;
+use harbor_client::db_models::FederationItem;
+use harbor_client::{CoreUIMsg, CoreUIMsgPacket, ReceiveSuccessMsg, SendSuccessMsg};
use iced::widget::qr_code::Data;
use iced::widget::row;
use iced::Element;
@@ -19,11 +24,6 @@ use std::collections::HashMap;
use std::str::FromStr;
use std::sync::Arc;
use uuid::Uuid;
-use harbor_client::db_models::FederationItem;
-use harbor_client::{CoreUIMsg, CoreUIMsgPacket, ReceiveSuccessMsg, SendSuccessMsg};
-use harbor_client::db_models::transaction_item::TransactionItem;
-use crate::bridge::run_core;
-use crate::components::focus_input_id;
pub mod bridge;
pub mod components;
@@ -199,6 +199,9 @@ pub struct HarborWallet {
current_receive_id: Option,
peek_status: PeekStatus,
add_federation_status: AddFederationStatus,
+ // Onboarding
+ show_add_a_mint_cta: bool,
+ has_navigated_to_mints: bool,
}
impl HarborWallet {
@@ -351,7 +354,15 @@ impl HarborWallet {
self.active_route = route;
}
},
- _ => self.active_route = route,
+ _ => match route {
+ Route::Mints(_) => {
+ // Hide the add a mint cta when navigating to mints
+ self.show_add_a_mint_cta = false;
+ self.has_navigated_to_mints = true;
+ self.active_route = route;
+ }
+ _ => self.active_route = route,
+ },
}
Task::none()
}
@@ -606,8 +617,8 @@ impl HarborWallet {
)
} else {
Task::perform(async {}, move |_| {
- Message::AddToast(Toast {
- title: "Failed to join mint".to_string(),
+ Message::AddToast(Toast {
+ title: "Failed to join mint".to_string(),
body: "Invalid invite code".to_string(),
status: ToastStatus::Bad,
})
@@ -617,14 +628,14 @@ impl HarborWallet {
Message::PeekFederation(invite_code) => {
let invite = InviteCode::from_str(&invite_code);
if let Ok(invite) = invite {
- self.peek_status = PeekStatus::Peeking;
- let id = Uuid::new_v4();
- Task::perform(
- Self::async_peek_federation(self.ui_handle.clone(), id, invite),
- |_| Message::Noop,
- )
- } else {
- Task::perform(async {}, |_| {
+ self.peek_status = PeekStatus::Peeking;
+ let id = Uuid::new_v4();
+ Task::perform(
+ Self::async_peek_federation(self.ui_handle.clone(), id, invite),
+ |_| Message::Noop,
+ )
+ } else {
+ Task::perform(async {}, |_| {
Message::AddToast(Toast {
title: "Failed to preview mint".to_string(),
body: "Invalid invite code".to_string(),
@@ -786,6 +797,9 @@ impl HarborWallet {
self.active_federation = list.first().cloned();
}
+ // Show the CTA if we have no federations and we haven't navigated to the mints page yet
+ self.show_add_a_mint_cta = list.is_empty() && !self.has_navigated_to_mints;
+
self.federation_list = list;
Task::none()
}
diff --git a/harbor-ui/src/routes/history.rs b/harbor-ui/src/routes/history.rs
index 9ef1509..506917b 100644
--- a/harbor-ui/src/routes/history.rs
+++ b/harbor-ui/src/routes/history.rs
@@ -1,4 +1,4 @@
-use iced::widget::column;
+use iced::widget::{column, text};
use iced::Element;
use crate::components::{basic_layout, h_header, h_screen_header, h_transaction_item, hr};
@@ -7,13 +7,17 @@ use crate::{HarborWallet, Message};
pub fn history(harbor: &HarborWallet) -> Element {
let header = h_header("History", "Here's what's happened so far.");
- let transactions = harbor
- .transaction_history
- .iter()
- .fold(column![], |column, item| {
- column.push(h_transaction_item(item)).push(hr())
- })
- .spacing(16);
+ let transactions = if harbor.transaction_history.is_empty() {
+ column![text("Nothing has happened yet.").size(18)]
+ } else {
+ harbor
+ .transaction_history
+ .iter()
+ .fold(column![], |column, item| {
+ column.push(h_transaction_item(item)).push(hr())
+ })
+ .spacing(16)
+ };
let column = column![header, transactions].spacing(48);
diff --git a/harbor-ui/src/routes/home.rs b/harbor-ui/src/routes/home.rs
index 63ba386..a58f651 100644
--- a/harbor-ui/src/routes/home.rs
+++ b/harbor-ui/src/routes/home.rs
@@ -9,11 +9,24 @@ use super::Route;
pub fn home(harbor: &HarborWallet) -> Element {
let formatted_balance = format_amount(harbor.balance_sats());
let balance = text(formatted_balance).size(64);
- let send_button =
- h_button("Send", SvgIcon::UpRight, false).on_press(Message::Navigate(Route::Send));
- let receive_button =
- h_button("Deposit", SvgIcon::DownLeft, false).on_press(Message::Navigate(Route::Receive));
- let buttons = row![receive_button, send_button].spacing(32);
+ let send_disabled = harbor.balance_sats() == 0;
+ let receive_disabled = harbor.active_federation.is_none();
+ let send_button = h_button("Send", SvgIcon::UpRight, false);
+ let receive_button = h_button("Deposit", SvgIcon::DownLeft, false);
+
+ let buttons = row![
+ if !send_disabled {
+ send_button.on_press(Message::Navigate(Route::Send))
+ } else {
+ send_button
+ },
+ if !receive_disabled {
+ receive_button.on_press(Message::Navigate(Route::Receive))
+ } else {
+ receive_button
+ }
+ ]
+ .spacing(32);
column![
h_screen_header(harbor, false),
diff --git a/harbor-ui/src/routes/mints.rs b/harbor-ui/src/routes/mints.rs
index 59a35fb..1dce565 100644
--- a/harbor-ui/src/routes/mints.rs
+++ b/harbor-ui/src/routes/mints.rs
@@ -12,7 +12,10 @@ fn mints_list(harbor: &HarborWallet) -> Element {
let list = if harbor.federation_list.is_empty() {
column![text("No federations added yet.").size(18)]
} else {
- let active_federation = harbor.active_federation.as_ref().expect("No active federation");
+ let active_federation = harbor
+ .active_federation
+ .as_ref()
+ .expect("No active federation");
harbor
.federation_list
@@ -47,8 +50,12 @@ fn mints_add(harbor: &HarborWallet) -> Element {
None,
);
- let peek_mint_button = h_button("Preview", SvgIcon::Eye, harbor.peek_status == PeekStatus::Peeking)
- .on_press(Message::PeekFederation(harbor.mint_invite_code_str.clone()));
+ let peek_mint_button = h_button(
+ "Preview",
+ SvgIcon::Eye,
+ harbor.peek_status == PeekStatus::Peeking,
+ )
+ .on_press(Message::PeekFederation(harbor.mint_invite_code_str.clone()));
column![header, mint_input, peek_mint_button].spacing(48)
}
@@ -59,7 +66,7 @@ fn mints_add(harbor: &HarborWallet) -> Element {
let add_mint_button = h_button(
"Add Mint",
SvgIcon::Plus,
- harbor.add_federation_status == AddFederationStatus::Adding
+ harbor.add_federation_status == AddFederationStatus::Adding,
)
.on_press(Message::AddFederation(harbor.mint_invite_code_str.clone()));
diff --git a/harbor-ui/src/routes/receive.rs b/harbor-ui/src/routes/receive.rs
index c18db21..af47d06 100644
--- a/harbor-ui/src/routes/receive.rs
+++ b/harbor-ui/src/routes/receive.rs
@@ -1,12 +1,12 @@
-use iced::widget::container::Style;
-use iced::widget::{column, container, qr_code, radio, row, text};
-use iced::Color;
-use iced::{Border, Element, Font};
-use harbor_client::ReceiveSuccessMsg;
use crate::components::{
basic_layout, h_button, h_caption_text, h_header, h_input, h_screen_header, mini_copy, SvgIcon,
};
use crate::{HarborWallet, Message, ReceiveMethod, ReceiveStatus};
+use harbor_client::ReceiveSuccessMsg;
+use iced::widget::container::Style;
+use iced::widget::{column, container, qr_code, radio, row, text};
+use iced::Color;
+use iced::{Border, Element, Font};
pub fn receive(harbor: &HarborWallet) -> Element {
let receive_string = harbor
diff --git a/harbor-ui/src/routes/settings.rs b/harbor-ui/src/routes/settings.rs
index 6ad84bd..13577cd 100644
--- a/harbor-ui/src/routes/settings.rs
+++ b/harbor-ui/src/routes/settings.rs
@@ -23,7 +23,7 @@ pub fn settings(harbor: &HarborWallet) -> Element {
let column = match (harbor.settings_show_seed_words, &harbor.seed_words) {
(true, Some(s)) => {
- let button = h_button("Hide Seed Words", SvgIcon::Squirrel, false)
+ let button = h_button("Hide Seed Words", SvgIcon::EyeClosed, false)
.on_press(Message::ShowSeedWords(false));
let words = text(s).size(24);
@@ -34,7 +34,7 @@ pub fn settings(harbor: &HarborWallet) -> Element {
column![header, button, words, copy_button]
}
_ => {
- let button = h_button("Show Seed Words", SvgIcon::Squirrel, false)
+ let button = h_button("Show Seed Words", SvgIcon::Eye, false)
.on_press(Message::ShowSeedWords(true));
column![