Skip to content

Commit

Permalink
chore: SECOND DRAFT toast
Browse files Browse the repository at this point in the history
  • Loading branch information
tvolk131 committed Sep 14, 2024
1 parent 3925084 commit c8ad7e3
Show file tree
Hide file tree
Showing 6 changed files with 107 additions and 104 deletions.
1 change: 1 addition & 0 deletions assets/icons/close.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
18 changes: 13 additions & 5 deletions src/app.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ use crate::{
db::Database,
fedimint::{FederationView, Wallet},
routes::{self, bitcoin_wallet, unlock, Loadable, Route, RouteName},
ui_components::{sidebar, Toast, ToastManager},
ui_components::{sidebar, Toast, ToastManager, ToastStatus},
};

#[derive(Debug, Clone)]
Expand Down Expand Up @@ -83,10 +83,18 @@ impl App {
Task::none()
}
Message::CopyStringToClipboard(text) => {
// TODO: Display a toast stating whether the copy succeeded or failed.
let _ = arboard::Clipboard::new().map(|mut clipboard| clipboard.set_text(text));

Task::none()
match arboard::Clipboard::new().map(|mut clipboard| clipboard.set_text(text)) {
Ok(_) => Task::done(Message::AddToast(Toast {
title: "Copied to clipboard".to_string(),
body: "The text has been copied to your clipboard.".to_string(),
status: ToastStatus::Good,
})),
Err(e) => Task::done(Message::AddToast(Toast {
title: "Failed to copy to clipboard".to_string(),
body: e.to_string(),
status: ToastStatus::Bad,
})),
}
}
Message::IncomingNip46Request(data) => {
if let Some(connected_state) = self.page.get_connected_state_mut() {
Expand Down
26 changes: 18 additions & 8 deletions src/routes/bitcoin_wallet/send.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ use crate::{
app,
fedimint::{FederationView, Wallet},
routes::{self, container, Loadable, RouteName},
ui_components::{icon_button, PaletteColor, SvgIcon},
ui_components::{icon_button, PaletteColor, SvgIcon, Toast, ToastStatus},
ConnectedState,
};

Expand All @@ -26,7 +26,7 @@ pub enum Message {
// Payment actions.
PayInvoice(Bolt11Invoice, FederationId),
PayInvoiceSucceeded(Bolt11Invoice),
PayInvoiceFailed(Bolt11Invoice),
PayInvoiceFailed((Bolt11Invoice, Arc<anyhow::Error>)),

UpdateFederationViews(BTreeMap<FederationId, FederationView>),
}
Expand Down Expand Up @@ -80,9 +80,11 @@ impl Page {
Ok(()) => app::Message::Routes(routes::Message::BitcoinWalletPage(
super::Message::Send(Message::PayInvoiceSucceeded(invoice)),
)),
// TODO: Display error to user. Probably a toast.
Err(_err) => app::Message::Routes(routes::Message::BitcoinWalletPage(
super::Message::Send(Message::PayInvoiceFailed(invoice)),
Err(err) => app::Message::Routes(routes::Message::BitcoinWalletPage(
super::Message::Send(Message::PayInvoiceFailed((
invoice,
Arc::from(err),
))),
)),
}
})
Expand All @@ -94,16 +96,24 @@ impl Page {
self.loadable_invoice_payment_or = Some(Loadable::Loaded(()));
}

Task::none()
Task::done(app::Message::AddToast(Toast {
title: "Payment succeeded".to_string(),
body: "Invoice was successfully paid".to_string(),
status: ToastStatus::Good,
}))
}
Message::PayInvoiceFailed(invoice) => {
Message::PayInvoiceFailed((invoice, err)) => {
let invoice_or = Bolt11Invoice::from_str(&self.lightning_invoice_input).ok();

if Some(invoice) == invoice_or {
self.loadable_invoice_payment_or = Some(Loadable::Failed);
}

Task::none()
Task::done(app::Message::AddToast(Toast {
title: "Payment failed".to_string(),
body: format!("Failed to pay invoice: {}", err),
status: ToastStatus::Bad,
}))
}
Message::UpdateFederationViews(federation_views) => {
self.federation_combo_box_selected_federation = self
Expand Down
43 changes: 43 additions & 0 deletions src/ui_components/button.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,49 @@ use crate::{

use super::{PaletteColor, SvgIcon};

pub fn mini_icon_button_no_text<'a>(
icon: SvgIcon,
palette_color: PaletteColor,
) -> Button<'a, app::Message, Theme> {
// TODO: Find a way to darken the icon color when the button is disabled.
let svg = icon.view(16.0, 16.0, Color::WHITE);

Button::new(svg)
.style(move |theme, status| {
let border = Border {
color: iced::Color::WHITE,
width: 0.0,
radius: (8.0).into(),
};

let mut bg_color = palette_color.to_color(theme);

if palette_color == PaletteColor::Background {
bg_color = darken(bg_color, 0.05);
}

bg_color = match status {
Status::Active => bg_color,
Status::Hovered => lighten(bg_color, 0.05),
Status::Pressed => lighten(bg_color, 0.1),
Status::Disabled => darken(bg_color, 0.5),
};

let mut text_color = Color::WHITE;
if status == Status::Disabled {
text_color = darken(text_color, 0.5);
}

button::Style {
background: Some(bg_color.into()),
text_color,
border,
shadow: Shadow::default(),
}
})
.padding(6)
}

pub fn icon_button(
text_str: &str,
icon: SvgIcon,
Expand Down
2 changes: 2 additions & 0 deletions src/ui_components/icon.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ pub enum SvgIcon {
ArrowUpward,
Casino,
ChevronRight,
Close,
ContentCopy,
CurrencyBitcoin,
Delete,
Expand Down Expand Up @@ -50,6 +51,7 @@ impl SvgIcon {
Self::ArrowUpward => icon_handle!("arrow_upward.svg"),
Self::Casino => icon_handle!("casino.svg"),
Self::ChevronRight => icon_handle!("chevron_right.svg"),
Self::Close => icon_handle!("close.svg"),
Self::ContentCopy => icon_handle!("content_copy.svg"),
Self::CurrencyBitcoin => icon_handle!("currency_bitcoin.svg"),
Self::Delete => icon_handle!("delete.svg"),
Expand Down
121 changes: 30 additions & 91 deletions src/ui_components/toast.rs
Original file line number Diff line number Diff line change
@@ -1,49 +1,58 @@
use std::fmt;
use std::time::{Duration, Instant};

use crate::app;
use crate::util::{darken, lighten};
use crate::util::lighten;
use iced::advanced::layout::{self, Layout};
use iced::advanced::overlay;
use iced::advanced::renderer;
use iced::advanced::widget::{self, Operation, Tree};
use iced::advanced::{Clipboard, Shell, Widget};
use iced::event::{self, Event};
use iced::widget::button::Status;
use iced::widget::{button, column, container, horizontal_space, row, text};
use iced::widget::{column, container, horizontal_space, row, text, vertical_space};
use iced::Border;
use iced::{mouse, Color, Font};
use iced::{window, Shadow};
use iced::{Alignment, Element, Length, Point, Rectangle, Renderer, Size, Theme, Vector};

use super::{icon_button, PaletteColor, SvgIcon};
use super::{mini_icon_button_no_text, PaletteColor, SvgIcon};

pub const DEFAULT_TIMEOUT: u64 = 5;
const DEFAULT_TIMEOUT: u64 = 5;

#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ToastStatus {
#[default]
Neutral,
Good,
Bad,
}

impl ToastStatus {
pub const ALL: &'static [Self] = &[Self::Neutral, Self::Good, Self::Bad];
}

impl fmt::Display for ToastStatus {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Neutral => "Neutral",
Self::Good => "Good",
Self::Bad => "Bad",
fn get_style(self, theme: &Theme) -> container::Style {
let gray = lighten(theme.palette().background, 0.1);

let border_color = match self {
Self::Neutral => gray,
Self::Good => theme.palette().success,
Self::Bad => theme.palette().danger,
};

container::Style {
background: Some(gray.into()),
text_color: Color::WHITE.into(),
border: Border {
color: border_color,
width: 1.,
radius: (4.).into(),
},
shadow: Shadow {
color: Color::from_rgba8(0, 0, 0, 0.25),
offset: Vector::new(-2., -2.),
blur_radius: 4.,
},
}
.fmt(f)
}
}

#[derive(Debug, Clone, Default)]
#[derive(Debug, Clone)]
pub struct Toast {
pub title: String,
pub body: String,
Expand All @@ -68,29 +77,7 @@ impl<'a> ToastManager<'a> {
.enumerate()
.map(|(index, toast)| {
let close_button =
icon_button("Close", SvgIcon::ArrowBack, PaletteColor::Background)
.style(|theme: &Theme, status| {
let border = Border {
color: Color::WHITE,
width: 0.,
radius: (4.).into(),
};

let background = match status {
Status::Hovered => darken(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(24.))
.height(Length::Fixed(24.));
mini_icon_button_no_text(SvgIcon::Close, PaletteColor::Background);

container(column![container(column![
row![
Expand All @@ -108,11 +95,7 @@ impl<'a> ToastManager<'a> {
])
.width(Length::Fill)
.padding(16)
.style(match toast.status {
ToastStatus::Neutral => neutral,
ToastStatus::Good => good,
ToastStatus::Bad => bad,
}),])
.style(|theme| toast.status.get_style(theme))])
.max_width(256)
.into()
})
Expand All @@ -125,13 +108,6 @@ impl<'a> ToastManager<'a> {
on_close: Box::new(on_close),
}
}

pub fn timeout(self, seconds: u64) -> Self {
Self {
timeout_secs: seconds,
..self
}
}
}

impl<'a> Widget<app::Message, Theme, Renderer> for ToastManager<'a> {
Expand Down Expand Up @@ -471,40 +447,3 @@ impl<'a> From<ToastManager<'a>> for Element<'a, app::Message> {
Element::new(manager)
}
}

fn styled(background: Color, border: Color) -> container::Style {
container::Style {
background: Some(background.into()),
text_color: Color::WHITE.into(),
border: Border {
color: border,
width: 1.,
radius: (4.).into(),
},
shadow: Shadow {
color: Color::from_rgba8(0, 0, 0, 0.25),
offset: Vector::new(-2., -2.),
blur_radius: 4.,
},
}
}

fn neutral(theme: &Theme) -> container::Style {
let gray = lighten(theme.palette().background, 0.1);

styled(gray, gray)
}

fn good(theme: &Theme) -> container::Style {
let gray = lighten(theme.palette().background, 0.1);
let green = Color::from_rgb8(40, 164, 127);

styled(gray, green)
}

fn bad(theme: &Theme) -> container::Style {
let gray = lighten(theme.palette().background, 0.1);
let red = theme.palette().primary;

styled(gray, red)
}

0 comments on commit c8ad7e3

Please sign in to comment.