Attach outpoint and txid labels for single payment transaction
This commit is contained in:
parent
bdac902307
commit
07fbc0c1b3
@ -26,34 +26,45 @@ impl LabelsEdited {
|
||||
targets: T,
|
||||
) -> Result<Command<Message>, Error> {
|
||||
match message {
|
||||
Message::View(view::Message::Label(labelled, msg)) => match msg {
|
||||
Message::View(view::Message::Label(items, msg)) => match msg {
|
||||
view::LabelMessage::Edited(value) => {
|
||||
let valid = value.len() <= 100;
|
||||
if let Some(label) = self.0.get_mut(&labelled) {
|
||||
label.valid = valid;
|
||||
label.value = value;
|
||||
} else {
|
||||
self.0.insert(labelled, form::Value { valid, value });
|
||||
for item in items {
|
||||
if let Some(label) = self.0.get_mut(&item) {
|
||||
label.valid = valid;
|
||||
label.value = value.clone();
|
||||
} else {
|
||||
self.0.insert(
|
||||
item,
|
||||
form::Value {
|
||||
valid,
|
||||
value: value.clone(),
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
view::LabelMessage::Cancel => {
|
||||
self.0.remove(&labelled);
|
||||
for item in items {
|
||||
self.0.remove(&item);
|
||||
}
|
||||
}
|
||||
view::LabelMessage::Confirm => {
|
||||
if let Some(label) = self.0.get(&labelled).cloned() {
|
||||
return Ok(Command::perform(
|
||||
async move {
|
||||
if let Some(item) = label_item_from_str(&labelled) {
|
||||
daemon.update_labels(&HashMap::from([(
|
||||
item,
|
||||
label.value.clone(),
|
||||
)]))?;
|
||||
}
|
||||
Ok(HashMap::from([(labelled, label.value)]))
|
||||
},
|
||||
Message::LabelsUpdated,
|
||||
));
|
||||
let mut updated_labels = HashMap::<LabelItem, String>::new();
|
||||
let mut updated_labels_str = HashMap::<String, String>::new();
|
||||
for item in items {
|
||||
if let Some(label) = self.0.get(&item).cloned() {
|
||||
updated_labels.insert(label_item_from_str(&item), label.value.clone());
|
||||
updated_labels_str.insert(item, label.value);
|
||||
}
|
||||
}
|
||||
return Ok(Command::perform(
|
||||
async move {
|
||||
daemon.update_labels(&updated_labels)?;
|
||||
Ok(updated_labels_str)
|
||||
},
|
||||
Message::LabelsUpdated,
|
||||
));
|
||||
}
|
||||
},
|
||||
Message::LabelsUpdated(res) => match res {
|
||||
@ -75,14 +86,14 @@ impl LabelsEdited {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn label_item_from_str(s: &str) -> Option<LabelItem> {
|
||||
pub fn label_item_from_str(s: &str) -> LabelItem {
|
||||
if let Ok(addr) = bitcoin::Address::from_str(s) {
|
||||
Some(LabelItem::Address(addr.assume_checked()))
|
||||
LabelItem::Address(addr.assume_checked())
|
||||
} else if let Ok(txid) = bitcoin::Txid::from_str(s) {
|
||||
Some(LabelItem::Txid(txid))
|
||||
LabelItem::Txid(txid)
|
||||
} else if let Ok(outpoint) = bitcoin::OutPoint::from_str(s) {
|
||||
Some(LabelItem::OutPoint(outpoint))
|
||||
LabelItem::OutPoint(outpoint)
|
||||
} else {
|
||||
None
|
||||
unreachable!()
|
||||
}
|
||||
}
|
||||
|
||||
@ -184,10 +184,7 @@ impl Action for SaveAction {
|
||||
let psbt = tx.psbt.clone();
|
||||
let mut labels = HashMap::<LabelItem, String>::new();
|
||||
for (item, label) in tx.labels() {
|
||||
labels.insert(
|
||||
label_item_from_str(item).expect("Must be a LabelItem"),
|
||||
label.clone(),
|
||||
);
|
||||
labels.insert(label_item_from_str(item), label.clone());
|
||||
}
|
||||
return Command::perform(
|
||||
async move {
|
||||
|
||||
@ -533,7 +533,7 @@ impl Step for SaveSpend {
|
||||
if !recipient.label.value.is_empty() {
|
||||
let label = recipient.label.value.clone();
|
||||
tx.labels
|
||||
.insert(tx.psbt.unsigned_tx.txid().to_string(), label.clone());
|
||||
.insert(tx.psbt.unsigned_tx.txid().to_string(), label);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -75,7 +75,25 @@ fn coin_list_view<'a>(
|
||||
.push(badge::coin())
|
||||
.push(if !collapsed {
|
||||
if let Some(label) = labels.get(&outpoint) {
|
||||
Container::new(p1_bold(label)).width(Length::Fill)
|
||||
if !label.is_empty() {
|
||||
Container::new(p1_bold(label)).width(Length::Fill)
|
||||
} else if let Some(label) = labels.get(&txid) {
|
||||
Container::new(
|
||||
Row::new()
|
||||
.spacing(5)
|
||||
.push(
|
||||
// It it not possible to know if a coin is a
|
||||
// change coin or not so for now, From is
|
||||
// enough
|
||||
p1_bold("From").style(color::GREY_3),
|
||||
)
|
||||
.push(p1_bold(label)),
|
||||
)
|
||||
.width(Length::Fill)
|
||||
} else {
|
||||
Container::new(Space::with_width(Length::Fill))
|
||||
.width(Length::Fill)
|
||||
}
|
||||
} else if let Some(label) = labels.get(&txid) {
|
||||
Container::new(
|
||||
Row::new()
|
||||
@ -124,10 +142,10 @@ fn coin_list_view<'a>(
|
||||
.spacing(5)
|
||||
.push(
|
||||
Container::new(if let Some(label) = labels_editing.get(&outpoint) {
|
||||
label::label_editing(outpoint.clone(), label, P1_SIZE)
|
||||
label::label_editing(vec![outpoint.clone()], label, P1_SIZE)
|
||||
} else {
|
||||
label::label_editable(
|
||||
outpoint.clone(),
|
||||
vec![outpoint.clone()],
|
||||
labels.get(&outpoint),
|
||||
P1_SIZE,
|
||||
)
|
||||
|
||||
@ -19,7 +19,7 @@ use crate::{
|
||||
menu::Menu,
|
||||
view::{coins, dashboard, label, message::Message},
|
||||
},
|
||||
daemon::model::HistoryTransaction,
|
||||
daemon::model::{HistoryTransaction, TransactionKind},
|
||||
};
|
||||
|
||||
pub const HISTORY_EVENT_PAGE_SIZE: u64 = 20;
|
||||
@ -103,7 +103,7 @@ pub fn home_view<'a>(
|
||||
.push(pending_events.iter().enumerate().fold(
|
||||
Column::new().spacing(10),
|
||||
|col, (i, event)| {
|
||||
if !event.is_self_send() {
|
||||
if !event.is_send_to_self() {
|
||||
col.push(event_list_view(i, event))
|
||||
} else {
|
||||
col
|
||||
@ -113,7 +113,7 @@ pub fn home_view<'a>(
|
||||
.push(events.iter().enumerate().fold(
|
||||
Column::new().spacing(10),
|
||||
|col, (i, event)| {
|
||||
if !event.is_self_send() {
|
||||
if !event.is_send_to_self() {
|
||||
col.push(event_list_view(i + pending_events.len(), event))
|
||||
} else {
|
||||
col
|
||||
@ -223,17 +223,33 @@ pub fn payment_view<'a>(
|
||||
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(match tx.kind {
|
||||
TransactionKind::OutgoingSinglePayment(_)
|
||||
| TransactionKind::OutgoingPaymentBatch(_) => {
|
||||
Container::new(h3("Outgoing payment")).width(Length::Fill)
|
||||
}
|
||||
TransactionKind::IncomingSinglePayment(_)
|
||||
| TransactionKind::IncomingPaymentBatch(_) => {
|
||||
Container::new(h3("Incoming payment")).width(Length::Fill)
|
||||
}
|
||||
_ => Container::new(h3("Payment")).width(Length::Fill),
|
||||
})
|
||||
.push(if let Some(label) = labels_editing.get(&outpoint) {
|
||||
label::label_editing(outpoint.clone(), label, H3_SIZE)
|
||||
.push(if tx.is_single_payment().is_some() {
|
||||
// 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
|
||||
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(&outpoint) {
|
||||
label::label_editing(vec![outpoint.clone()], label, H3_SIZE)
|
||||
} else {
|
||||
label::label_editable(outpoint.clone(), tx.labels.get(&outpoint), H1_SIZE)
|
||||
label::label_editable(vec![outpoint.clone()], tx.labels.get(&outpoint), H3_SIZE)
|
||||
})
|
||||
.push(Container::new(amount_with_size(
|
||||
&Amount::from_sat(tx.tx.output[output_index].value),
|
||||
@ -241,10 +257,18 @@ pub fn payment_view<'a>(
|
||||
)))
|
||||
.push(Space::with_height(H3_SIZE))
|
||||
.push(Container::new(h3("Transaction")).width(Length::Fill))
|
||||
.push(if let Some(label) = labels_editing.get(&txid) {
|
||||
label::label_editing(txid.clone(), label, H3_SIZE)
|
||||
.push_maybe(if tx.is_batch() {
|
||||
if let Some(label) = labels_editing.get(&txid) {
|
||||
Some(label::label_editing(vec![txid.clone()], label, H3_SIZE))
|
||||
} else {
|
||||
Some(label::label_editable(
|
||||
vec![txid.clone()],
|
||||
tx.labels.get(&txid),
|
||||
H3_SIZE,
|
||||
))
|
||||
}
|
||||
} else {
|
||||
label::label_editable(txid.clone(), tx.labels.get(&txid), H3_SIZE)
|
||||
None
|
||||
})
|
||||
.push_maybe(tx.fee_amount.map(|fee_amount| {
|
||||
Row::new()
|
||||
|
||||
@ -10,7 +10,7 @@ use liana_ui::{
|
||||
use crate::app::view;
|
||||
|
||||
pub fn label_editable(
|
||||
labelled: String,
|
||||
labelled: Vec<String>,
|
||||
label: Option<&String>,
|
||||
size: u16,
|
||||
) -> Element<'_, view::Message> {
|
||||
@ -50,7 +50,7 @@ pub fn label_editable(
|
||||
}
|
||||
|
||||
pub fn label_editing(
|
||||
labelled: String,
|
||||
labelled: Vec<String>,
|
||||
label: &form::Value<String>,
|
||||
size: u16,
|
||||
) -> Element<view::Message> {
|
||||
|
||||
@ -9,7 +9,7 @@ pub enum Message {
|
||||
Close,
|
||||
Select(usize),
|
||||
SelectSub(usize, usize),
|
||||
Label(String, LabelMessage),
|
||||
Label(Vec<String>, LabelMessage),
|
||||
Settings(SettingsMessage),
|
||||
CreateSpend(CreateSpendMessage),
|
||||
ImportSpend(ImportSpendMessage),
|
||||
|
||||
@ -199,9 +199,9 @@ pub fn spend_header<'a>(
|
||||
Column::new()
|
||||
.spacing(20)
|
||||
.push(if let Some(label) = labels_editing.get(&txid) {
|
||||
label::label_editing(txid.clone(), label, H3_SIZE)
|
||||
label::label_editing(vec![txid.clone()], label, H3_SIZE)
|
||||
} else {
|
||||
label::label_editable(txid.clone(), tx.labels.get(&txid), H1_SIZE)
|
||||
label::label_editable(vec![txid.clone()], tx.labels.get(&txid), H1_SIZE)
|
||||
})
|
||||
.push(
|
||||
Column::new()
|
||||
@ -738,10 +738,10 @@ fn input_view<'a>(
|
||||
.align_items(Alignment::Center)
|
||||
.push(
|
||||
Container::new(if let Some(label) = labels_editing.get(&outpoint) {
|
||||
label::label_editing(outpoint.clone(), label, text::P1_SIZE)
|
||||
label::label_editing(vec![outpoint.clone()], label, text::P1_SIZE)
|
||||
} else {
|
||||
label::label_editable(
|
||||
outpoint.clone(),
|
||||
vec![outpoint.clone()],
|
||||
labels.get(&outpoint),
|
||||
text::P1_SIZE,
|
||||
)
|
||||
@ -825,10 +825,10 @@ fn payment_view<'a>(
|
||||
.align_items(Alignment::Center)
|
||||
.push(
|
||||
Container::new(if let Some(label) = labels_editing.get(&outpoint) {
|
||||
label::label_editing(outpoint.clone(), label, text::P1_SIZE)
|
||||
label::label_editing(vec![outpoint.clone()], label, text::P1_SIZE)
|
||||
} else {
|
||||
label::label_editable(
|
||||
outpoint.clone(),
|
||||
vec![outpoint.clone()],
|
||||
labels.get(&outpoint),
|
||||
text::P1_SIZE,
|
||||
)
|
||||
|
||||
@ -52,10 +52,14 @@ pub fn receive<'a>(
|
||||
card::simple(
|
||||
Column::new()
|
||||
.push(if let Some(label) = labels_editing.get(&addr) {
|
||||
label::label_editing(addr.clone(), label, text::P1_SIZE)
|
||||
label::label_editing(
|
||||
vec![addr.clone()],
|
||||
label,
|
||||
text::P1_SIZE,
|
||||
)
|
||||
} else {
|
||||
label::label_editable(
|
||||
addr.clone(),
|
||||
vec![addr.clone()],
|
||||
labels.get(&addr),
|
||||
text::P1_SIZE,
|
||||
)
|
||||
|
||||
@ -92,7 +92,7 @@ fn tx_list_view(i: usize, tx: &HistoryTransaction) -> Element<'_, Message> {
|
||||
Row::new()
|
||||
.push(if tx.is_external() {
|
||||
badge::receive()
|
||||
} else if tx.is_self_send() {
|
||||
} else if tx.is_send_to_self() {
|
||||
badge::cycle()
|
||||
} else {
|
||||
badge::spend()
|
||||
@ -165,22 +165,35 @@ pub fn tx_view<'a>(
|
||||
cache,
|
||||
warning,
|
||||
Column::new()
|
||||
.push(if tx.is_self_send() {
|
||||
.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(label) = labels_editing.get(&txid) {
|
||||
label::label_editing(txid.clone(), label, H3_SIZE)
|
||||
.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(txid.clone(), tx.labels.get(&txid), H1_SIZE)
|
||||
label::label_editable(vec![txid.clone()], tx.labels.get(&txid), H1_SIZE)
|
||||
})
|
||||
.push(
|
||||
Column::new().spacing(20).push(
|
||||
Column::new()
|
||||
.push(if tx.is_self_send() {
|
||||
.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))
|
||||
|
||||
@ -193,6 +193,7 @@ pub struct HistoryTransaction {
|
||||
pub fee_amount: Option<Amount>,
|
||||
pub height: Option<i32>,
|
||||
pub time: Option<u32>,
|
||||
pub kind: TransactionKind,
|
||||
}
|
||||
|
||||
impl HistoryTransaction {
|
||||
@ -228,6 +229,47 @@ impl HistoryTransaction {
|
||||
|
||||
Self {
|
||||
labels: HashMap::new(),
|
||||
kind: if coins.is_empty() {
|
||||
if change_indexes.len() == 1 {
|
||||
TransactionKind::IncomingSinglePayment(OutPoint {
|
||||
txid: tx.txid(),
|
||||
vout: change_indexes[0] as u32,
|
||||
})
|
||||
} else {
|
||||
TransactionKind::IncomingPaymentBatch(
|
||||
change_indexes
|
||||
.iter()
|
||||
.map(|i| OutPoint {
|
||||
txid: tx.txid(),
|
||||
vout: *i as u32,
|
||||
})
|
||||
.collect(),
|
||||
)
|
||||
}
|
||||
} else if outgoing_amount == Amount::from_sat(0) {
|
||||
TransactionKind::SendToSelf
|
||||
} else {
|
||||
let outpoints: Vec<OutPoint> = tx
|
||||
.output
|
||||
.iter()
|
||||
.enumerate()
|
||||
.filter_map(|(i, _)| {
|
||||
if !change_indexes.contains(&i) {
|
||||
Some(OutPoint {
|
||||
txid: tx.txid(),
|
||||
vout: i as u32,
|
||||
})
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
if outpoints.len() == 1 {
|
||||
TransactionKind::OutgoingSinglePayment(outpoints[0])
|
||||
} else {
|
||||
TransactionKind::OutgoingPaymentBatch(outpoints)
|
||||
}
|
||||
},
|
||||
tx,
|
||||
coins,
|
||||
change_indexes,
|
||||
@ -241,24 +283,41 @@ impl HistoryTransaction {
|
||||
}
|
||||
|
||||
pub fn is_external(&self) -> bool {
|
||||
self.coins.is_empty()
|
||||
matches!(
|
||||
self.kind,
|
||||
TransactionKind::IncomingSinglePayment(_) | TransactionKind::IncomingPaymentBatch(_)
|
||||
)
|
||||
}
|
||||
|
||||
pub fn is_self_send(&self) -> bool {
|
||||
!self.coins.is_empty() && self.outgoing_amount == Amount::from_sat(0)
|
||||
pub fn is_send_to_self(&self) -> bool {
|
||||
matches!(self.kind, TransactionKind::SendToSelf)
|
||||
}
|
||||
|
||||
pub fn is_single_payment(&self) -> Option<OutPoint> {
|
||||
match self.kind {
|
||||
TransactionKind::IncomingSinglePayment(outpoint) => Some(outpoint),
|
||||
TransactionKind::OutgoingSinglePayment(outpoint) => Some(outpoint),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_batch(&self) -> bool {
|
||||
self.tx
|
||||
.output
|
||||
.iter()
|
||||
.enumerate()
|
||||
.filter(|(i, _)| !self.change_indexes.contains(i))
|
||||
.count()
|
||||
> 1
|
||||
matches!(
|
||||
self.kind,
|
||||
TransactionKind::IncomingPaymentBatch(_) | TransactionKind::OutgoingPaymentBatch(_)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum TransactionKind {
|
||||
IncomingSinglePayment(OutPoint),
|
||||
IncomingPaymentBatch(Vec<OutPoint>),
|
||||
SendToSelf,
|
||||
OutgoingSinglePayment(OutPoint),
|
||||
OutgoingPaymentBatch(Vec<OutPoint>),
|
||||
}
|
||||
|
||||
impl Labelled for HistoryTransaction {
|
||||
fn labels(&mut self) -> &mut HashMap<String, String> {
|
||||
&mut self.labels
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user