Use payment model in the home
This commit is contained in:
parent
b1abce5a24
commit
790beaa670
@ -40,6 +40,8 @@ pub enum Message {
|
||||
HardwareWallets(HardwareWalletMessage),
|
||||
HistoryTransactionsExtension(Result<Vec<HistoryTransaction>, Error>),
|
||||
HistoryTransactions(Result<Vec<HistoryTransaction>, Error>),
|
||||
Payments(Result<Vec<Payment>, Error>),
|
||||
PaymentsExtension(Result<Vec<Payment>, Error>),
|
||||
Payment(Result<(HistoryTransaction, usize), Error>),
|
||||
LabelsUpdated(Result<HashMap<String, Option<String>>, Error>),
|
||||
BroadcastModal(Result<HashSet<Txid>, Error>),
|
||||
|
||||
@ -7,6 +7,7 @@ use iced::Command;
|
||||
use liana_ui::widget::Element;
|
||||
use lianad::commands::CoinStatus;
|
||||
|
||||
use crate::daemon::model::LabelsLoader;
|
||||
use crate::{
|
||||
app::{
|
||||
cache::Cache,
|
||||
@ -132,7 +133,7 @@ impl State for CoinsPanel {
|
||||
match self.labels_edited.update(
|
||||
daemon,
|
||||
message,
|
||||
std::iter::once(&mut self.coins).map(|a| a as &mut dyn Labelled),
|
||||
std::iter::once(&mut self.coins).map(|a| a as &mut dyn LabelsLoader),
|
||||
) {
|
||||
Ok(cmd) => return cmd,
|
||||
Err(e) => {
|
||||
|
||||
@ -5,7 +5,7 @@ use std::{collections::HashMap, iter::IntoIterator, sync::Arc};
|
||||
use crate::{
|
||||
app::{error::Error, message::Message, view},
|
||||
daemon::{
|
||||
model::{LabelItem, Labelled, LabelsLoader},
|
||||
model::{LabelItem, LabelsLoader},
|
||||
Daemon,
|
||||
},
|
||||
};
|
||||
@ -19,7 +19,7 @@ impl LabelsEdited {
|
||||
pub fn cache(&self) -> &HashMap<String, form::Value<String>> {
|
||||
&self.0
|
||||
}
|
||||
pub fn update<'a, T: IntoIterator<Item = &'a mut dyn Labelled>>(
|
||||
pub fn update<'a, T: IntoIterator<Item = &'a mut dyn LabelsLoader>>(
|
||||
&mut self,
|
||||
daemon: Arc<dyn Daemon + Sync + Send>,
|
||||
message: Message,
|
||||
|
||||
@ -28,8 +28,9 @@ use super::{
|
||||
|
||||
pub const HISTORY_EVENT_PAGE_SIZE: u64 = 20;
|
||||
|
||||
use crate::daemon::model::LabelsLoader;
|
||||
use crate::daemon::{
|
||||
model::{remaining_sequence, Coin, HistoryTransaction, Labelled},
|
||||
model::{remaining_sequence, Coin, HistoryTransaction, Payment},
|
||||
Daemon,
|
||||
};
|
||||
pub use coins::CoinsPanel;
|
||||
@ -127,7 +128,7 @@ pub struct Home {
|
||||
unconfirmed_balance: Amount,
|
||||
remaining_sequence: Option<u32>,
|
||||
expiring_coins: Vec<OutPoint>,
|
||||
events: Vec<HistoryTransaction>,
|
||||
events: Vec<Payment>,
|
||||
is_last_page: bool,
|
||||
processing: bool,
|
||||
selected_event: Option<(HistoryTransaction, usize)>,
|
||||
@ -170,7 +171,7 @@ impl State for Home {
|
||||
if let Some((tx, output_index)) = &self.selected_event {
|
||||
view::home::payment_view(
|
||||
cache,
|
||||
&tx,
|
||||
tx,
|
||||
*output_index,
|
||||
self.labels_edited.cache(),
|
||||
self.warning.as_ref(),
|
||||
@ -217,7 +218,7 @@ impl State for Home {
|
||||
);
|
||||
}
|
||||
},
|
||||
Message::HistoryTransactions(res) => match res {
|
||||
Message::Payments(res) => match res {
|
||||
Err(e) => self.warning = Some(e),
|
||||
Ok(events) => {
|
||||
self.warning = None;
|
||||
@ -225,7 +226,7 @@ impl State for Home {
|
||||
self.is_last_page = (self.events.len() as u64) < HISTORY_EVENT_PAGE_SIZE;
|
||||
}
|
||||
},
|
||||
Message::HistoryTransactionsExtension(res) => match res {
|
||||
Message::PaymentsExtension(res) => match res {
|
||||
Err(e) => self.warning = Some(e),
|
||||
Ok(events) => {
|
||||
self.processing = false;
|
||||
@ -235,13 +236,13 @@ impl State for Home {
|
||||
if let Some(position) = self
|
||||
.events
|
||||
.iter()
|
||||
.position(|event2| event2.txid == event.txid)
|
||||
.position(|event2| event2.outpoint == event.outpoint)
|
||||
{
|
||||
let len = self.events.len();
|
||||
for event in events {
|
||||
if !self.events[position..len]
|
||||
.iter()
|
||||
.any(|event2| event2.txid == event.txid)
|
||||
.any(|event2| event2.outpoint == event.outpoint)
|
||||
{
|
||||
self.events.push(event);
|
||||
}
|
||||
@ -274,12 +275,11 @@ impl State for Home {
|
||||
self.warning = Some(e);
|
||||
}
|
||||
},
|
||||
Message::View(view::Message::SelectSub(i, j)) => {
|
||||
let txid = self.events[i].txid;
|
||||
Message::View(view::Message::SelectPayment(outpoint)) => {
|
||||
return Command::perform(
|
||||
async move {
|
||||
let tx = daemon.get_history_txs(&[txid]).await?.remove(0);
|
||||
Ok((tx, j))
|
||||
let tx = daemon.get_history_txs(&[outpoint.txid]).await?.remove(0);
|
||||
Ok((tx, outpoint.vout as usize))
|
||||
},
|
||||
Message::Payment,
|
||||
);
|
||||
@ -290,11 +290,11 @@ impl State for Home {
|
||||
message,
|
||||
self.events
|
||||
.iter_mut()
|
||||
.map(|tx| tx as &mut dyn Labelled)
|
||||
.map(|tx| tx as &mut dyn LabelsLoader)
|
||||
.chain(
|
||||
self.selected_event
|
||||
.iter_mut()
|
||||
.map(|(tx, _)| tx as &mut dyn Labelled),
|
||||
.map(|(tx, _)| tx as &mut dyn LabelsLoader),
|
||||
),
|
||||
) {
|
||||
Ok(cmd) => {
|
||||
@ -319,9 +319,10 @@ impl State for Home {
|
||||
self.processing = true;
|
||||
return Command::perform(
|
||||
async move {
|
||||
let last_event_date = last_event_date.timestamp() as u32;
|
||||
let mut limit = HISTORY_EVENT_PAGE_SIZE;
|
||||
let mut events = daemon
|
||||
.list_history_txs(0_u32, last_event_date, limit)
|
||||
.list_confirmed_payments(0_u32, last_event_date, limit)
|
||||
.await?;
|
||||
|
||||
// because gethistory cursor is inclusive and use blocktime
|
||||
@ -344,12 +345,14 @@ impl State for Home {
|
||||
{
|
||||
// increments of the equivalent of one page more.
|
||||
limit += HISTORY_EVENT_PAGE_SIZE;
|
||||
events = daemon.list_history_txs(0, last_event_date, limit).await?;
|
||||
events = daemon
|
||||
.list_confirmed_payments(0, last_event_date, limit)
|
||||
.await?;
|
||||
}
|
||||
events.sort_by(|a, b| a.compare(b));
|
||||
Ok(events)
|
||||
},
|
||||
Message::HistoryTransactionsExtension,
|
||||
Message::PaymentsExtension,
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -382,16 +385,16 @@ impl State for Home {
|
||||
Command::batch(vec![
|
||||
Command::perform(
|
||||
async move {
|
||||
let mut txs = daemon
|
||||
.list_history_txs(0, now, HISTORY_EVENT_PAGE_SIZE)
|
||||
let mut payments = daemon
|
||||
.list_confirmed_payments(0, now, HISTORY_EVENT_PAGE_SIZE)
|
||||
.await?;
|
||||
txs.sort_by(|a, b| a.compare(b));
|
||||
payments.sort_by(|a, b| a.compare(b));
|
||||
|
||||
let mut pending_txs = daemon.list_pending_txs().await?;
|
||||
pending_txs.extend(txs);
|
||||
Ok(pending_txs)
|
||||
let mut pending_payments = daemon.list_pending_payments().await?;
|
||||
pending_payments.extend(payments);
|
||||
Ok(pending_payments)
|
||||
},
|
||||
Message::HistoryTransactions,
|
||||
Message::Payments,
|
||||
),
|
||||
Command::perform(
|
||||
async move {
|
||||
|
||||
@ -18,6 +18,7 @@ use liana_ui::{
|
||||
widget::Element,
|
||||
};
|
||||
|
||||
use crate::daemon::model::LabelsLoader;
|
||||
use crate::{
|
||||
app::{
|
||||
cache::Cache,
|
||||
@ -193,7 +194,7 @@ impl PsbtState {
|
||||
match self.labels_edited.update(
|
||||
daemon,
|
||||
message,
|
||||
std::iter::once(&mut self.tx).map(|tx| tx as &mut dyn Labelled),
|
||||
std::iter::once(&mut self.tx).map(|tx| tx as &mut dyn LabelsLoader),
|
||||
) {
|
||||
Ok(cmd) => {
|
||||
return cmd;
|
||||
|
||||
@ -9,6 +9,7 @@ use liana::miniscript::bitcoin::{
|
||||
};
|
||||
use liana_ui::{component::modal, widget::*};
|
||||
|
||||
use crate::daemon::model::LabelsLoader;
|
||||
use crate::{
|
||||
app::{
|
||||
cache::Cache,
|
||||
@ -117,7 +118,7 @@ impl State for ReceivePanel {
|
||||
match self.labels_edited.update(
|
||||
daemon,
|
||||
message,
|
||||
std::iter::once(&mut self.addresses).map(|a| a as &mut dyn Labelled),
|
||||
std::iter::once(&mut self.addresses).map(|a| a as &mut dyn LabelsLoader),
|
||||
) {
|
||||
Ok(cmd) => cmd,
|
||||
Err(e) => {
|
||||
|
||||
@ -27,7 +27,7 @@ use crate::{
|
||||
view,
|
||||
wallet::Wallet,
|
||||
},
|
||||
daemon::model,
|
||||
daemon::model::{self, LabelsLoader},
|
||||
};
|
||||
|
||||
use crate::daemon::{
|
||||
@ -192,11 +192,14 @@ impl State for TransactionsPanel {
|
||||
match self.labels_edited.update(
|
||||
daemon,
|
||||
message,
|
||||
self.txs.iter_mut().map(|tx| tx as &mut dyn Labelled).chain(
|
||||
self.selected_tx
|
||||
.iter_mut()
|
||||
.map(|tx| tx as &mut dyn Labelled),
|
||||
),
|
||||
self.txs
|
||||
.iter_mut()
|
||||
.map(|tx| tx as &mut dyn LabelsLoader)
|
||||
.chain(
|
||||
self.selected_tx
|
||||
.iter_mut()
|
||||
.map(|tx| tx as &mut dyn LabelsLoader),
|
||||
),
|
||||
) {
|
||||
Ok(cmd) => {
|
||||
return cmd;
|
||||
|
||||
@ -23,7 +23,7 @@ use crate::{
|
||||
view::{coins, dashboard, label, message::Message},
|
||||
wallet::SyncStatus,
|
||||
},
|
||||
daemon::model::{HistoryTransaction, TransactionKind},
|
||||
daemon::model::{HistoryTransaction, Payment, PaymentKind, TransactionKind},
|
||||
};
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
@ -32,7 +32,7 @@ pub fn home_view<'a>(
|
||||
unconfirmed_balance: &'a bitcoin::Amount,
|
||||
remaining_sequence: &Option<u32>,
|
||||
expiring_coins: &[bitcoin::OutPoint],
|
||||
events: &'a [HistoryTransaction],
|
||||
events: &'a [Payment],
|
||||
is_last_page: bool,
|
||||
processing: bool,
|
||||
sync_status: &SyncStatus,
|
||||
@ -147,16 +147,9 @@ pub fn home_view<'a>(
|
||||
Column::new()
|
||||
.spacing(10)
|
||||
.push(h4_bold("Last payments"))
|
||||
.push(events.iter().enumerate().fold(
|
||||
Column::new().spacing(10),
|
||||
|col, (i, event)| {
|
||||
if !event.is_send_to_self() {
|
||||
col.push(event_list_view(i, event))
|
||||
} else {
|
||||
col
|
||||
}
|
||||
},
|
||||
))
|
||||
.push(events.iter().fold(Column::new().spacing(10), |col, event| {
|
||||
col.push(event_list_view(event))
|
||||
}))
|
||||
.push_maybe(if !is_last_page && !events.is_empty() {
|
||||
Some(
|
||||
Container::new(
|
||||
@ -189,62 +182,48 @@ pub fn home_view<'a>(
|
||||
.into()
|
||||
}
|
||||
|
||||
fn event_list_view(i: usize, event: &HistoryTransaction) -> Column<'_, Message> {
|
||||
event.tx.output.iter().enumerate().fold(
|
||||
Column::new().spacing(10),
|
||||
|col, (output_index, output)| {
|
||||
let label = if let Some(label) = event.labels.get(
|
||||
&bitcoin::OutPoint {
|
||||
txid: event.tx.txid(),
|
||||
vout: output_index as u32,
|
||||
}
|
||||
.to_string(),
|
||||
) {
|
||||
Some(p1_regular(label))
|
||||
} else if let Ok(addr) =
|
||||
bitcoin::Address::from_script(&output.script_pubkey, event.network)
|
||||
{
|
||||
event.labels.get(&addr.to_string()).map(|label| {
|
||||
p1_regular(format!("address label: {}", label)).style(color::GREY_3)
|
||||
})
|
||||
} else {
|
||||
None
|
||||
};
|
||||
if event.is_external() {
|
||||
if !event.change_indexes.contains(&output_index) {
|
||||
col
|
||||
} else if let Some(t) = event.time {
|
||||
col.push(event::confirmed_incoming_event(
|
||||
label,
|
||||
DateTime::<Utc>::from_timestamp(t as i64, 0).unwrap(),
|
||||
&output.value,
|
||||
Message::SelectSub(i, output_index),
|
||||
))
|
||||
} else {
|
||||
col.push(event::unconfirmed_incoming_event(
|
||||
label,
|
||||
&output.value,
|
||||
Message::SelectSub(i, output_index),
|
||||
))
|
||||
}
|
||||
} else if event.change_indexes.contains(&output_index) {
|
||||
col
|
||||
} else if let Some(t) = event.time {
|
||||
col.push(event::confirmed_outgoing_event(
|
||||
label,
|
||||
DateTime::<Utc>::from_timestamp(t as i64, 0).unwrap(),
|
||||
&output.value,
|
||||
Message::SelectSub(i, output_index),
|
||||
))
|
||||
} else {
|
||||
col.push(event::unconfirmed_outgoing_event(
|
||||
label,
|
||||
&output.value,
|
||||
Message::SelectSub(i, output_index),
|
||||
))
|
||||
}
|
||||
},
|
||||
)
|
||||
fn event_list_view(event: &Payment) -> Element<'_, Message> {
|
||||
let label = if let Some(label) = &event.label {
|
||||
Some(p1_regular(label))
|
||||
} else {
|
||||
event
|
||||
.address_label
|
||||
.as_ref()
|
||||
.map(|label| p1_regular(format!("address label: {}", label)).style(color::GREY_3))
|
||||
};
|
||||
if event.kind == PaymentKind::Incoming {
|
||||
if let Some(t) = event.time {
|
||||
event::confirmed_incoming_event(
|
||||
label,
|
||||
t,
|
||||
&event.amount,
|
||||
Message::SelectPayment(event.outpoint),
|
||||
)
|
||||
.into()
|
||||
} else {
|
||||
event::unconfirmed_incoming_event(
|
||||
label,
|
||||
&event.amount,
|
||||
Message::SelectPayment(event.outpoint),
|
||||
)
|
||||
.into()
|
||||
}
|
||||
} else if let Some(t) = event.time {
|
||||
event::confirmed_outgoing_event(
|
||||
label,
|
||||
t,
|
||||
&event.amount,
|
||||
Message::SelectPayment(event.outpoint),
|
||||
)
|
||||
.into()
|
||||
} else {
|
||||
event::unconfirmed_outgoing_event(
|
||||
label,
|
||||
&event.amount,
|
||||
Message::SelectPayment(event.outpoint),
|
||||
)
|
||||
.into()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn payment_view<'a>(
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
use crate::{app::menu::Menu, node::bitcoind::RpcAuthType};
|
||||
use liana::miniscript::bitcoin::bip32::Fingerprint;
|
||||
use liana::miniscript::bitcoin::{bip32::Fingerprint, OutPoint};
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum Message {
|
||||
@ -8,7 +8,7 @@ pub enum Message {
|
||||
Menu(Menu),
|
||||
Close,
|
||||
Select(usize),
|
||||
SelectSub(usize, usize),
|
||||
SelectPayment(OutPoint),
|
||||
Label(Vec<String>, LabelMessage),
|
||||
Settings(SettingsMessage),
|
||||
CreateSpend(CreateSpendMessage),
|
||||
|
||||
@ -323,6 +323,37 @@ pub trait Daemon: Debug {
|
||||
load_labels(self, &mut txs).await?;
|
||||
Ok(txs)
|
||||
}
|
||||
|
||||
async fn list_pending_payments(&self) -> Result<Vec<model::Payment>, DaemonError> {
|
||||
let mut txs = self.list_pending_txs().await?;
|
||||
txs.sort_by(|a, b| b.time.cmp(&a.time));
|
||||
let events = txs.into_iter().fold(Vec::new(), |mut array, tx| {
|
||||
let mut events = model::payments_from_tx(tx);
|
||||
array.append(&mut events);
|
||||
array
|
||||
});
|
||||
|
||||
Ok(events)
|
||||
}
|
||||
|
||||
/// returns a sorted list of payments.
|
||||
async fn list_confirmed_payments(
|
||||
&self,
|
||||
start: u32,
|
||||
end: u32,
|
||||
limit: u64,
|
||||
) -> Result<Vec<model::Payment>, DaemonError> {
|
||||
let mut txs = self.list_history_txs(start, end, limit).await?;
|
||||
txs.sort_by(|a, b| b.time.cmp(&a.time));
|
||||
let events = txs.into_iter().fold(Vec::new(), |mut array, tx| {
|
||||
let mut events = model::payments_from_tx(tx);
|
||||
array.append(&mut events);
|
||||
array
|
||||
});
|
||||
|
||||
Ok(events)
|
||||
}
|
||||
|
||||
/// Implemented by LianaLite backend
|
||||
async fn update_wallet_metadata(
|
||||
&self,
|
||||
|
||||
@ -408,6 +408,96 @@ impl HistoryTransaction {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Payment {
|
||||
pub label: Option<String>,
|
||||
pub address: Option<String>,
|
||||
pub address_label: Option<String>,
|
||||
pub amount: Amount,
|
||||
pub outpoint: OutPoint,
|
||||
pub time: Option<chrono::DateTime<chrono::Utc>>,
|
||||
pub kind: PaymentKind,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub enum PaymentKind {
|
||||
Outgoing,
|
||||
Incoming,
|
||||
}
|
||||
|
||||
impl Payment {
|
||||
pub fn compare(&self, other: &Self) -> Ordering {
|
||||
match (&self.time, &other.time) {
|
||||
// `None` values come first
|
||||
(None, Some(_)) => Ordering::Less,
|
||||
(Some(_), None) => Ordering::Greater,
|
||||
// Both are `None`, so we consider them equal
|
||||
(None, None) => self
|
||||
.outpoint
|
||||
.txid
|
||||
.cmp(&other.outpoint.txid)
|
||||
.then_with(|| self.outpoint.vout.cmp(&other.outpoint.vout)),
|
||||
// Both are `Some`, compare by descending time, then by txid
|
||||
(Some(time1), Some(time2)) => time2
|
||||
.cmp(time1)
|
||||
.then_with(|| self.outpoint.txid.cmp(&other.outpoint.txid))
|
||||
.then_with(|| self.outpoint.vout.cmp(&other.outpoint.vout)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl LabelsLoader for Payment {
|
||||
fn load_labels(&mut self, new_labels: &HashMap<String, Option<String>>) {
|
||||
if let Some(label) = self.address.as_ref().and_then(|addr| new_labels.get(addr)) {
|
||||
self.address_label = label.clone();
|
||||
}
|
||||
if let Some(label) = new_labels.get(&self.outpoint.to_string()) {
|
||||
self.label = label.clone();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn payments_from_tx(history_tx: HistoryTransaction) -> Vec<Payment> {
|
||||
let time = history_tx
|
||||
.time
|
||||
.map(|t| chrono::DateTime::<chrono::Utc>::from_timestamp(t as i64, 0).unwrap());
|
||||
history_tx
|
||||
.tx
|
||||
.output
|
||||
.iter()
|
||||
.enumerate()
|
||||
.fold(Vec::new(), |mut array, (output_index, output)| {
|
||||
if history_tx.is_external() && !history_tx.change_indexes.contains(&output_index) {
|
||||
return array;
|
||||
}
|
||||
let outpoint = OutPoint {
|
||||
txid: history_tx.tx.txid(),
|
||||
vout: output_index as u32,
|
||||
};
|
||||
let label = history_tx.labels.get(&outpoint.to_string()).cloned();
|
||||
let address = Address::from_script(&output.script_pubkey, history_tx.network)
|
||||
.ok()
|
||||
.map(|addr| addr.to_string());
|
||||
let address_label = address
|
||||
.as_ref()
|
||||
.and_then(|addr| history_tx.labels.get(addr).cloned());
|
||||
array.push(Payment {
|
||||
label,
|
||||
address,
|
||||
address_label,
|
||||
outpoint,
|
||||
time,
|
||||
amount: output.value,
|
||||
kind: if history_tx.is_external() {
|
||||
PaymentKind::Incoming
|
||||
} else {
|
||||
PaymentKind::Outgoing
|
||||
},
|
||||
});
|
||||
array
|
||||
})
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum TransactionKind {
|
||||
IncomingSinglePayment(OutPoint),
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user