diff --git a/gui/src/app/state/mod.rs b/gui/src/app/state/mod.rs index c7170b61..753116f9 100644 --- a/gui/src/app/state/mod.rs +++ b/gui/src/app/state/mod.rs @@ -53,7 +53,7 @@ pub struct Home { number_of_expiring_coins: usize, pending_events: Vec, events: Vec, - selected_event: Option, + selected_event: Option<(usize, usize)>, warning: Option, } @@ -87,32 +87,28 @@ impl Home { impl State for Home { fn view<'a>(&'a self, cache: &'a Cache) -> Element<'a, view::Message> { - if let Some(i) = self.selected_event { + if let Some((i, output_index)) = self.selected_event { let event = if i < self.pending_events.len() { &self.pending_events[i] } else { &self.events[i - self.pending_events.len()] }; - return view::modal( - false, - self.warning.as_ref(), - view::transactions::tx_view(cache, event), - None::>, - ); + view::home::payment_view(cache, event, output_index, self.warning.as_ref()) + } else { + view::dashboard( + &Menu::Home, + cache, + None, + view::home::home_view( + &self.balance, + &self.unconfirmed_balance, + &self.remaining_sequence, + self.number_of_expiring_coins, + &self.pending_events, + &self.events, + ), + ) } - view::dashboard( - &Menu::Home, - cache, - None, - view::home::home_view( - &self.balance, - &self.unconfirmed_balance, - &self.remaining_sequence, - self.number_of_expiring_coins, - &self.pending_events, - &self.events, - ), - ) } fn update( @@ -180,8 +176,8 @@ impl State for Home { Message::View(view::Message::Close) => { self.selected_event = None; } - Message::View(view::Message::Select(i)) => { - self.selected_event = Some(i); + Message::View(view::Message::SelectSub(i, j)) => { + self.selected_event = Some((i, j)); } Message::View(view::Message::Next) => { if let Some(last) = self.events.last() { diff --git a/gui/src/app/state/transactions.rs b/gui/src/app/state/transactions.rs index a61740c1..c6104379 100644 --- a/gui/src/app/state/transactions.rs +++ b/gui/src/app/state/transactions.rs @@ -5,7 +5,7 @@ use std::time::{SystemTime, UNIX_EPOCH}; use iced::Command; use liana_ui::widget::*; -use crate::app::{cache::Cache, error::Error, menu::Menu, message::Message, view, State}; +use crate::app::{cache::Cache, error::Error, message::Message, view, State}; use crate::daemon::{model::HistoryTransaction, Daemon}; @@ -36,19 +36,15 @@ impl State for TransactionsPanel { } else { &self.txs[i - self.pending_txs.len()] }; - return view::modal( - false, + view::transactions::tx_view(cache, tx, self.warning.as_ref()) + } else { + view::transactions::transactions_view( + cache, + &self.pending_txs, + &self.txs, self.warning.as_ref(), - view::transactions::tx_view(cache, tx), - None::>, - ); + ) } - view::dashboard( - &Menu::Transactions, - cache, - None, - view::transactions::transactions_view(&self.pending_txs, &self.txs), - ) } fn update( diff --git a/gui/src/app/view/home.rs b/gui/src/app/view/home.rs index 24f1125e..2802a3da 100644 --- a/gui/src/app/view/home.rs +++ b/gui/src/app/view/home.rs @@ -5,14 +5,19 @@ use iced::{alignment, Alignment, Length}; use liana::miniscript::bitcoin; use liana_ui::{ color, - component::{amount::*, event, text::*}, + component::{amount::*, card, event, text::*}, icon, theme, util::Collection, widget::*, }; use crate::{ - app::view::{coins, message::Message}, + app::{ + cache::Cache, + error::Error, + menu::Menu, + view::{coins, dashboard, message::Message}, + }, daemon::model::HistoryTransaction, }; @@ -143,12 +148,12 @@ fn event_list_view<'a>(i: usize, event: &HistoryTransaction) -> Column<'a, Messa col.push(event::confirmed_incoming_event( NaiveDateTime::from_timestamp_opt(t as i64, 0).unwrap(), &Amount::from_sat(output.value), - Message::Select(i), + Message::SelectSub(i, output_index), )) } else { col.push(event::unconfirmed_incoming_event( &Amount::from_sat(output.value), - Message::Select(i), + Message::SelectSub(i, output_index), )) } } else if event.change_indexes.contains(&output_index) { @@ -157,14 +162,92 @@ fn event_list_view<'a>(i: usize, event: &HistoryTransaction) -> Column<'a, Messa col.push(event::confirmed_outgoing_event( NaiveDateTime::from_timestamp_opt(t as i64, 0).unwrap(), &Amount::from_sat(output.value), - Message::Select(i), + Message::SelectSub(i, output_index), )) } else { col.push(event::unconfirmed_outgoing_event( &Amount::from_sat(output.value), - Message::Select(i), + Message::SelectSub(i, output_index), )) } }, ) } + +pub fn payment_view<'a>( + cache: &'a Cache, + tx: &'a HistoryTransaction, + output_index: usize, + warning: Option<&'a Error>, +) -> Element<'a, Message> { + dashboard( + &Menu::Home, + cache, + warning, + Column::new() + .push(if tx.is_self_send() { + Container::new(h3("Payment")).width(Length::Fill) + } else if tx.is_external() { + Container::new(h3("Incoming payment")).width(Length::Fill) + } else { + Container::new(h3("Outgoing payment")).width(Length::Fill) + }) + .push(Container::new(amount_with_size( + &Amount::from_sat(tx.tx.output[output_index].value), + H1_SIZE, + ))) + .push(Container::new(h3("Transaction")).width(Length::Fill)) + .push_maybe(tx.fee_amount.map(|fee_amount| { + Row::new() + .align_items(Alignment::Center) + .push(h3("Miner fee: ").style(color::GREY_3)) + .push(amount_with_size(&fee_amount, H3_SIZE)) + })) + .push(card::simple( + Column::new() + .push_maybe(tx.time.map(|t| { + let date = NaiveDateTime::from_timestamp_opt(t as i64, 0) + .unwrap() + .format("%b. %d, %Y - %T"); + Row::new() + .width(Length::Fill) + .push(Container::new(text("Date:").bold()).width(Length::Fill)) + .push(Container::new(text(format!("{}", date))).width(Length::Shrink)) + })) + .push( + Row::new() + .width(Length::Fill) + .align_items(Alignment::Center) + .push(Container::new(text("Txid:").bold()).width(Length::Fill)) + .push( + Row::new() + .align_items(Alignment::Center) + .push(Container::new(text(format!("{}", tx.tx.txid())).small())) + .push( + Button::new(icon::clipboard_icon()) + .on_press(Message::Clipboard(tx.tx.txid().to_string())) + .style(theme::Button::TransparentBorder), + ) + .width(Length::Shrink), + ), + ) + .spacing(5), + )) + .push(super::psbt::inputs_and_outputs_view( + &tx.coins, + &tx.tx, + cache.network, + if tx.is_external() { + None + } else { + Some(tx.change_indexes.clone()) + }, + if tx.is_external() { + Some(tx.change_indexes.clone()) + } else { + None + }, + )) + .spacing(20), + ) +} diff --git a/gui/src/app/view/message.rs b/gui/src/app/view/message.rs index 0c589f58..4359067e 100644 --- a/gui/src/app/view/message.rs +++ b/gui/src/app/view/message.rs @@ -8,6 +8,7 @@ pub enum Message { Menu(Menu), Close, Select(usize), + SelectSub(usize, usize), Settings(SettingsMessage), CreateSpend(CreateSpendMessage), ImportSpend(ImportSpendMessage), diff --git a/gui/src/app/view/mod.rs b/gui/src/app/view/mod.rs index 36450c41..8c7e221a 100644 --- a/gui/src/app/view/mod.rs +++ b/gui/src/app/view/mod.rs @@ -34,7 +34,7 @@ use crate::app::{cache::Cache, error::Error, menu::Menu}; pub fn sidebar<'a>(menu: &Menu, cache: &'a Cache) -> Container<'a, Message> { let home_button = if *menu == Menu::Home { button::menu_active(Some(home_icon()), "Home") - .on_press(Message::Reload) + .on_press(Message::Menu(Menu::Home)) .width(iced::Length::Fill) } else { button::menu(Some(home_icon()), "Home") @@ -53,7 +53,7 @@ pub fn sidebar<'a>(menu: &Menu, cache: &'a Cache) -> Container<'a, Message> { .align_items(iced::Alignment::Center), ) .style(theme::Button::Menu(true)) - .on_press(Message::Reload) + .on_press(Message::Menu(Menu::Transactions)) .width(iced::Length::Fill) } else { Button::new( diff --git a/gui/src/app/view/transactions.rs b/gui/src/app/view/transactions.rs index e2ac8f01..e3edde66 100644 --- a/gui/src/app/view/transactions.rs +++ b/gui/src/app/view/transactions.rs @@ -3,6 +3,7 @@ use chrono::NaiveDateTime; use iced::{alignment, Alignment, Length}; use liana_ui::{ + color, component::{amount::*, badge, card, text::*}, icon, theme, util::Collection, @@ -10,65 +11,76 @@ use liana_ui::{ }; use crate::{ - app::{cache::Cache, view::message::Message}, + app::{ + cache::Cache, + error::Error, + menu::Menu, + view::{dashboard, message::Message}, + }, daemon::model::HistoryTransaction, }; pub const HISTORY_EVENT_PAGE_SIZE: u64 = 20; pub fn transactions_view<'a>( + cache: &'a Cache, pending_txs: &[HistoryTransaction], txs: &Vec, + warning: Option<&'a Error>, ) -> Element<'a, Message> { - Column::new() - .push(Container::new(h3("Transactions")).width(Length::Fill)) - .push( - Column::new() - .spacing(10) - .push_maybe(if !pending_txs.is_empty() { - Some( - pending_txs - .iter() - .enumerate() - .fold(Column::new().spacing(10), |col, (i, tx)| { - col.push(tx_list_view(i, tx)) - }), - ) - } else { - None - }) - .push( - txs.iter() - .enumerate() - .fold(Column::new().spacing(10), |col, (i, tx)| { - col.push(tx_list_view(i + pending_txs.len(), tx)) - }), - ) - .push_maybe( - if txs.len() % HISTORY_EVENT_PAGE_SIZE as usize == 0 && !txs.is_empty() { + dashboard( + &Menu::Transactions, + cache, + warning, + Column::new() + .push(Container::new(h3("Transactions")).width(Length::Fill)) + .push( + Column::new() + .spacing(10) + .push_maybe(if !pending_txs.is_empty() { Some( - Container::new( - Button::new( - text("See more") - .width(Length::Fill) - .horizontal_alignment(alignment::Horizontal::Center), - ) - .width(Length::Fill) - .padding(15) - .style(theme::Button::TransparentBorder) - .on_press(Message::Next), - ) - .width(Length::Fill) - .style(theme::Container::Card(theme::Card::Simple)), + pending_txs + .iter() + .enumerate() + .fold(Column::new().spacing(10), |col, (i, tx)| { + col.push(tx_list_view(i, tx)) + }), ) } else { None - }, - ), - ) - .align_items(Alignment::Center) - .spacing(30) - .into() + }) + .push( + txs.iter() + .enumerate() + .fold(Column::new().spacing(10), |col, (i, tx)| { + col.push(tx_list_view(i + pending_txs.len(), tx)) + }), + ) + .push_maybe( + if txs.len() % HISTORY_EVENT_PAGE_SIZE as usize == 0 && !txs.is_empty() { + Some( + Container::new( + Button::new( + text("See more") + .width(Length::Fill) + .horizontal_alignment(alignment::Horizontal::Center), + ) + .width(Length::Fill) + .padding(15) + .style(theme::Button::TransparentBorder) + .on_press(Message::Next), + ) + .width(Length::Fill) + .style(theme::Container::Card(theme::Card::Simple)), + ) + } else { + None + }, + ), + ) + .align_items(Alignment::Center) + .spacing(30), + ) } fn tx_list_view<'a>(i: usize, tx: &HistoryTransaction) -> Element<'a, Message> { @@ -125,74 +137,86 @@ fn tx_list_view<'a>(i: usize, tx: &HistoryTransaction) -> Element<'a, Message> { .into() } -pub fn tx_view<'a>(cache: &Cache, tx: &'a HistoryTransaction) -> Element<'a, Message> { - Column::new() - .push( - Row::new() - .push(if tx.is_external() { - badge::receive() - } else { - badge::spend() - }) - .spacing(10) - .align_items(Alignment::Center), - ) - .push(if tx.is_external() { - amount_with_size(&tx.incoming_amount, 50) - } else { - amount_with_size(&tx.outgoing_amount, 50) - }) - .push_maybe( - tx.fee_amount - .map(|fee| Row::new().push(text("Miner Fee: ")).push(amount(&fee))), - ) - .push(card::simple( - Column::new() - .push_maybe(tx.time.map(|t| { - let date = NaiveDateTime::from_timestamp_opt(t as i64, 0) - .unwrap() - .format("%b. %d, %Y - %T"); - Row::new() - .width(Length::Fill) - .push(Container::new(text("Date:").bold()).width(Length::Fill)) - .push(Container::new(text(format!("{}", date))).width(Length::Shrink)) - })) - .push( - Row::new() - .width(Length::Fill) - .align_items(Alignment::Center) - .push(Container::new(text("Txid:").bold()).width(Length::Fill)) - .push( +pub fn tx_view<'a>( + cache: &'a Cache, + tx: &'a HistoryTransaction, + warning: Option<&'a Error>, +) -> Element<'a, Message> { + dashboard( + &Menu::Transactions, + cache, + warning, + Column::new() + .push(if tx.is_self_send() { + Container::new(h3("Transaction")).width(Length::Fill) + } else if tx.is_external() { + Container::new(h3("Incoming transaction")).width(Length::Fill) + } else { + Container::new(h3("Outgoing transaction")).width(Length::Fill) + }) + .push( + Column::new().spacing(20).push( + Column::new() + .push(if tx.is_self_send() { + Container::new(h1("Self send")) + } else if tx.is_external() { + Container::new(amount_with_size(&tx.incoming_amount, H1_SIZE)) + } else { + Container::new(amount_with_size(&tx.outgoing_amount, H1_SIZE)) + }) + .push_maybe(tx.fee_amount.map(|fee_amount| { Row::new() .align_items(Alignment::Center) - .push(Container::new(text(format!("{}", tx.tx.txid())).small())) - .push( - Button::new(icon::clipboard_icon()) - .on_press(Message::Clipboard(tx.tx.txid().to_string())) - .style(theme::Button::TransparentBorder), - ) - .width(Length::Shrink), - ), - ) - .spacing(5), - )) - .push(super::psbt::inputs_and_outputs_view( - &tx.coins, - &tx.tx, - cache.network, - if tx.is_external() { - None - } else { - Some(tx.change_indexes.clone()) - }, - if tx.is_external() { - Some(tx.change_indexes.clone()) - } else { - None - }, - )) - .align_items(Alignment::Center) - .spacing(20) - .max_width(800) - .into() + .push(h3("Miner fee: ").style(color::GREY_3)) + .push(amount_with_size(&fee_amount, H3_SIZE)) + })), + ), + ) + .push(card::simple( + Column::new() + .push_maybe(tx.time.map(|t| { + let date = NaiveDateTime::from_timestamp_opt(t as i64, 0) + .unwrap() + .format("%b. %d, %Y - %T"); + Row::new() + .width(Length::Fill) + .push(Container::new(text("Date:").bold()).width(Length::Fill)) + .push(Container::new(text(format!("{}", date))).width(Length::Shrink)) + })) + .push( + Row::new() + .width(Length::Fill) + .align_items(Alignment::Center) + .push(Container::new(text("Txid:").bold()).width(Length::Fill)) + .push( + Row::new() + .align_items(Alignment::Center) + .push(Container::new(text(format!("{}", tx.tx.txid())).small())) + .push( + Button::new(icon::clipboard_icon()) + .on_press(Message::Clipboard(tx.tx.txid().to_string())) + .style(theme::Button::TransparentBorder), + ) + .width(Length::Shrink), + ), + ) + .spacing(5), + )) + .push(super::psbt::inputs_and_outputs_view( + &tx.coins, + &tx.tx, + cache.network, + if tx.is_external() { + None + } else { + Some(tx.change_indexes.clone()) + }, + if tx.is_external() { + Some(tx.change_indexes.clone()) + } else { + None + }, + )) + .spacing(20), + ) }