liana/gui/src/app/view/transactions.rs
edouardparis d56a9a8a40 fix single payment output label
When a transaction has only one payment,
then its txid label is attached to the payment label.
While modifying the label of an output it did not change the
whole transaction label, which is a bug.
2023-10-31 15:11:43 +01:00

268 lines
11 KiB
Rust

use chrono::NaiveDateTime;
use std::collections::HashMap;
use iced::{alignment, Alignment, Length};
use liana_ui::{
color,
component::{amount::*, badge, card, form, text::*},
icon, theme,
util::Collection,
widget::*,
};
use crate::{
app::{
cache::Cache,
error::Error,
menu::Menu,
view::{dashboard, label, message::Message},
},
daemon::model::HistoryTransaction,
};
pub const HISTORY_EVENT_PAGE_SIZE: u64 = 20;
pub fn transactions_view<'a>(
cache: &'a Cache,
pending_txs: &'a [HistoryTransaction],
txs: &'a Vec<HistoryTransaction>,
warning: Option<&'a Error>,
) -> Element<'a, Message> {
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(
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() {
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(i: usize, tx: &HistoryTransaction) -> Element<'_, Message> {
Container::new(
Button::new(
Row::new()
.push(
Row::new()
.push(if tx.is_external() {
badge::receive()
} else if tx.is_send_to_self() {
badge::cycle()
} else {
badge::spend()
})
.push(
Column::new()
.push_maybe(
tx.labels.get(&tx.tx.txid().to_string()).map(p1_regular),
)
.push_maybe(tx.time.map(|t| {
Container::new(
text(format!(
"{}",
NaiveDateTime::from_timestamp_opt(t as i64, 0)
.unwrap()
.format("%b. %d, %Y - %T"),
))
.style(color::GREY_3)
.small(),
)
})),
)
.spacing(10)
.align_items(Alignment::Center)
.width(Length::Fill),
)
.push_maybe(if tx.time.is_none() {
Some(badge::unconfirmed())
} else {
None
})
.push_maybe(if tx.is_batch() {
Some(badge::batch())
} else {
None
})
.push(if tx.is_external() {
Row::new()
.spacing(5)
.push(text("+"))
.push(amount(&tx.incoming_amount))
.align_items(Alignment::Center)
} else if tx.outgoing_amount != Amount::from_sat(0) {
Row::new()
.spacing(5)
.push(text("-"))
.push(amount(&tx.outgoing_amount))
.align_items(Alignment::Center)
} else {
Row::new().push(text("Self-transfer"))
})
.align_items(Alignment::Center)
.spacing(20),
)
.padding(10)
.on_press(Message::Select(i))
.style(theme::Button::TransparentBorder),
)
.style(theme::Container::Card(theme::Card::Simple))
.into()
}
pub fn tx_view<'a>(
cache: &'a Cache,
tx: &'a HistoryTransaction,
labels_editing: &'a HashMap<String, form::Value<String>>,
warning: Option<&'a Error>,
) -> Element<'a, Message> {
let txid = tx.tx.txid().to_string();
dashboard(
&Menu::Transactions,
cache,
warning,
Column::new()
.push(if tx.is_send_to_self() {
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(if let Some(outpoint) = tx.is_single_payment() {
// if the payment is a payment of a single payment transaction then
// the label of the transaction is attached to the label of the payment outpoint
let outpoint = outpoint.to_string();
if let Some(label) = labels_editing.get(&outpoint) {
label::label_editing(vec![outpoint.clone(), txid.clone()], label, H3_SIZE)
} else {
label::label_editable(
vec![outpoint.clone(), txid.clone()],
tx.labels.get(&outpoint),
H3_SIZE,
)
}
} else if let Some(label) = labels_editing.get(&txid) {
label::label_editing(vec![txid.clone()], label, H3_SIZE)
} else {
label::label_editable(vec![txid.clone()], tx.labels.get(&txid), H1_SIZE)
})
.push(
Column::new().spacing(20).push(
Column::new()
.push(if tx.is_send_to_self() {
Container::new(h1("Self-transfer"))
} 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(h3("Miner fee: ").style(color::GREY_3))
.push(amount_with_size(&fee_amount, H3_SIZE))
.push(text(" ").size(H3_SIZE))
.push(
text(format!(
"({} sats/vbyte)",
fee_amount.to_sat() / tx.tx.vsize() as u64
))
.size(H4_SIZE)
.style(color::GREY_3),
)
})),
),
)
.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(txid.clone()).small()))
.push(
Button::new(icon::clipboard_icon())
.on_press(Message::Clipboard(txid.clone()))
.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())
},
&tx.labels,
labels_editing,
tx.is_single_payment().is_some(),
))
.spacing(20),
)
}