mirror of
https://github.com/sparrowwallet/sparrow.git
synced 2024-12-24 12:46:45 +00:00
address table with hash indexes
This commit is contained in:
parent
9aa8b10898
commit
70c4eeaf5e
10 changed files with 232 additions and 20 deletions
|
@ -6,7 +6,10 @@ import com.sparrowwallet.drongo.protocol.Transaction;
|
|||
import com.sparrowwallet.sparrow.EventManager;
|
||||
import com.sparrowwallet.sparrow.event.ReceiveActionEvent;
|
||||
import com.sparrowwallet.sparrow.event.ReceiveToEvent;
|
||||
import com.sparrowwallet.sparrow.event.TransactionViewEvent;
|
||||
import com.sparrowwallet.sparrow.glyphfont.FontAwesome5;
|
||||
import com.sparrowwallet.sparrow.wallet.Entry;
|
||||
import com.sparrowwallet.sparrow.wallet.HashIndexEntry;
|
||||
import com.sparrowwallet.sparrow.wallet.NodeEntry;
|
||||
import javafx.application.Platform;
|
||||
import javafx.beans.property.ReadOnlyObjectWrapper;
|
||||
|
@ -56,9 +59,9 @@ public class AddressTreeTable extends TreeTableView<Entry> {
|
|||
labelCol.setSortable(false);
|
||||
getColumns().add(labelCol);
|
||||
|
||||
TreeTableColumn<Entry, Long> amountCol = new TreeTableColumn<>("Amount");
|
||||
TreeTableColumn<Entry, Long> amountCol = new TreeTableColumn<>("Value");
|
||||
amountCol.setCellValueFactory((TreeTableColumn.CellDataFeatures<Entry, Long> param) -> {
|
||||
return new ReadOnlyObjectWrapper<>(param.getValue().getValue().getAmount());
|
||||
return new ReadOnlyObjectWrapper<>(param.getValue().getValue().getValue());
|
||||
});
|
||||
amountCol.setCellFactory(p -> new AmountCell());
|
||||
amountCol.setSortable(false);
|
||||
|
@ -76,16 +79,36 @@ public class AddressTreeTable extends TreeTableView<Entry> {
|
|||
setColumnResizePolicy(TreeTableView.CONSTRAINED_RESIZE_POLICY);
|
||||
}
|
||||
|
||||
private static void applyRowStyles(TreeTableCell<?, ?> cell, Entry entry) {
|
||||
cell.getStyleClass().remove("node-row");
|
||||
cell.getStyleClass().remove("hashindex-row");
|
||||
cell.getStyleClass().remove("spent");
|
||||
|
||||
if(entry != null) {
|
||||
if(entry instanceof NodeEntry) {
|
||||
cell.getStyleClass().add("node-row");
|
||||
} else if(entry instanceof HashIndexEntry) {
|
||||
cell.getStyleClass().add("hashindex-row");
|
||||
HashIndexEntry hashIndexEntry = (HashIndexEntry)entry;
|
||||
if(hashIndexEntry.isSpent()) {
|
||||
cell.getStyleClass().add("spent");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static class DataCell extends TreeTableCell<Entry, Entry> {
|
||||
public DataCell() {
|
||||
super();
|
||||
getStyleClass().add("address-cell");
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void updateItem(Entry entry, boolean empty) {
|
||||
super.updateItem(entry, empty);
|
||||
|
||||
applyRowStyles(this, entry);
|
||||
getStyleClass().remove("address-cell");
|
||||
|
||||
if(empty) {
|
||||
setText(null);
|
||||
setGraphic(null);
|
||||
|
@ -95,8 +118,14 @@ public class AddressTreeTable extends TreeTableView<Entry> {
|
|||
Address address = nodeEntry.getAddress();
|
||||
setText(address.toString());
|
||||
setContextMenu(new AddressContextMenu(address));
|
||||
} else {
|
||||
//TODO: Add transaction outpoint
|
||||
getStyleClass().add("address-cell");
|
||||
} else if(entry instanceof HashIndexEntry) {
|
||||
HashIndexEntry hashIndexEntry = (HashIndexEntry)entry;
|
||||
setText(hashIndexEntry.getDescription());
|
||||
setContextMenu(new HashIndexEntryContextMenu(hashIndexEntry));
|
||||
Tooltip tooltip = new Tooltip();
|
||||
tooltip.setText(hashIndexEntry.getHashIndex().toString());
|
||||
setTooltip(tooltip);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -124,6 +153,21 @@ public class AddressTreeTable extends TreeTableView<Entry> {
|
|||
}
|
||||
}
|
||||
|
||||
private static class HashIndexEntryContextMenu extends ContextMenu {
|
||||
public HashIndexEntryContextMenu(HashIndexEntry hashIndexEntry) {
|
||||
String label = "Copy " + (hashIndexEntry.getType().equals(HashIndexEntry.Type.OUTPUT) ? "Transaction Output" : "Transaction Input");
|
||||
MenuItem copyHashIndex = new MenuItem(label);
|
||||
copyHashIndex.setOnAction(AE -> {
|
||||
hide();
|
||||
ClipboardContent content = new ClipboardContent();
|
||||
content.putString(hashIndexEntry.getHashIndex().toString());
|
||||
Clipboard.getSystemClipboard().setContent(content);
|
||||
});
|
||||
|
||||
getItems().add(copyHashIndex);
|
||||
}
|
||||
}
|
||||
|
||||
private static class LabelCell extends TextFieldTreeTableCell<Entry, String> {
|
||||
public LabelCell() {
|
||||
super(new DefaultStringConverter());
|
||||
|
@ -138,6 +182,8 @@ public class AddressTreeTable extends TreeTableView<Entry> {
|
|||
setText(null);
|
||||
setGraphic(null);
|
||||
} else {
|
||||
applyRowStyles(this, getTreeTableView().getTreeItem(getIndex()).getValue());
|
||||
|
||||
setText(label);
|
||||
setContextMenu(new LabelContextMenu(label));
|
||||
}
|
||||
|
@ -212,16 +258,15 @@ public class AddressTreeTable extends TreeTableView<Entry> {
|
|||
setText(null);
|
||||
setGraphic(null);
|
||||
} else {
|
||||
String satsValue = String.format(Locale.ENGLISH, "%,d", amount) + " sats";
|
||||
String btcValue = CoinLabel.BTC_FORMAT.format(amount.doubleValue() / Transaction.SATOSHIS_PER_BITCOIN) + " BTC";
|
||||
applyRowStyles(this, getTreeTableView().getTreeItem(getIndex()).getValue());
|
||||
|
||||
String satsValue = String.format(Locale.ENGLISH, "%,d", amount);
|
||||
String btcValue = CoinLabel.getBTCFormat().format(amount.doubleValue() / Transaction.SATOSHIS_PER_BITCOIN) + " BTC";
|
||||
|
||||
Tooltip tooltip = new Tooltip();
|
||||
if(amount > CoinLabel.MAX_SATS_SHOWN) {
|
||||
tooltip.setText(satsValue);
|
||||
setText(btcValue);
|
||||
} else {
|
||||
tooltip.setText(btcValue);
|
||||
|
||||
setText(satsValue);
|
||||
}
|
||||
setTooltip(tooltip);
|
||||
}
|
||||
}
|
||||
|
@ -230,6 +275,7 @@ public class AddressTreeTable extends TreeTableView<Entry> {
|
|||
private static class ActionCell extends TreeTableCell<Entry, Entry> {
|
||||
private final HBox actionBox;
|
||||
private final Button receiveButton;
|
||||
private final Button viewTransactionButton;
|
||||
|
||||
public ActionCell() {
|
||||
super();
|
||||
|
@ -248,6 +294,15 @@ public class AddressTreeTable extends TreeTableView<Entry> {
|
|||
EventManager.get().post(new ReceiveActionEvent(nodeEntry));
|
||||
Platform.runLater(() -> EventManager.get().post(new ReceiveToEvent(nodeEntry)));
|
||||
});
|
||||
|
||||
viewTransactionButton = new Button("");
|
||||
Glyph searchGlyph = new Glyph(FontAwesome5.FONT_NAME, FontAwesome5.Glyph.SEARCH);
|
||||
searchGlyph.setFontSize(12);
|
||||
viewTransactionButton.setGraphic(searchGlyph);
|
||||
viewTransactionButton.setOnAction(event -> {
|
||||
HashIndexEntry hashIndexEntry = (HashIndexEntry)getTreeTableView().getTreeItem(getIndex()).getValue();
|
||||
EventManager.get().post(new TransactionViewEvent(hashIndexEntry.getBlockTransaction(), hashIndexEntry));
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -256,9 +311,13 @@ public class AddressTreeTable extends TreeTableView<Entry> {
|
|||
if (empty) {
|
||||
setGraphic(null);
|
||||
} else {
|
||||
applyRowStyles(this, getTreeTableView().getTreeItem(getIndex()).getValue());
|
||||
|
||||
actionBox.getChildren().remove(0, actionBox.getChildren().size());
|
||||
if(entry instanceof NodeEntry) {
|
||||
actionBox.getChildren().add(receiveButton);
|
||||
} else if(entry instanceof HashIndexEntry) {
|
||||
actionBox.getChildren().add(viewTransactionButton);
|
||||
}
|
||||
|
||||
setGraphic(actionBox);
|
||||
|
|
|
@ -85,4 +85,9 @@ public class CoinLabel extends CopyableLabel {
|
|||
getItems().addAll(copySatsValue, copyBtcValue);
|
||||
}
|
||||
}
|
||||
|
||||
public static DecimalFormat getBTCFormat() {
|
||||
BTC_FORMAT.setMaximumFractionDigits(8);
|
||||
return BTC_FORMAT;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,31 @@
|
|||
package com.sparrowwallet.sparrow.control;
|
||||
|
||||
import java.text.DateFormat;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Date;
|
||||
|
||||
public class DateLabel extends CopyableLabel {
|
||||
private static final DateFormat LAST_24_HR = new SimpleDateFormat("HH:mm");
|
||||
private static final DateFormat LAST_YEAR = new SimpleDateFormat("d MMM");
|
||||
private static final DateFormat ALL_TIME = new SimpleDateFormat("yyyy/MM/dd");
|
||||
|
||||
private Date date;
|
||||
|
||||
public DateLabel(Date date) {
|
||||
super(getShortDateFormat(date));
|
||||
this.date = date;
|
||||
}
|
||||
|
||||
public static String getShortDateFormat(Date date) {
|
||||
Date now = new Date();
|
||||
long elapsed = (now.getTime() - date.getTime()) / 1000;
|
||||
|
||||
if(elapsed < 24 * 60 * 60) {
|
||||
return LAST_24_HR.format(date);
|
||||
} else if(elapsed < 365 * 24 * 60 * 60) {
|
||||
return LAST_YEAR.format(date);
|
||||
} else {
|
||||
return ALL_TIME.format(date);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -37,7 +37,7 @@ public class RecursiveTreeItem<T> extends TreeItem<T> {
|
|||
}
|
||||
});
|
||||
|
||||
this.setExpanded(true);
|
||||
this.setExpanded(false);
|
||||
}
|
||||
|
||||
private void addChildrenListener(T value){
|
||||
|
|
|
@ -0,0 +1,26 @@
|
|||
package com.sparrowwallet.sparrow.event;
|
||||
|
||||
import com.sparrowwallet.drongo.wallet.BlockTransaction;
|
||||
import com.sparrowwallet.sparrow.wallet.HashIndexEntry;
|
||||
|
||||
public class TransactionViewEvent {
|
||||
public final BlockTransaction blockTransaction;
|
||||
public final HashIndexEntry hashIndexEntry;
|
||||
|
||||
public TransactionViewEvent(BlockTransaction blockTransaction) {
|
||||
this(blockTransaction, null);
|
||||
}
|
||||
|
||||
public TransactionViewEvent(BlockTransaction blockTransaction, HashIndexEntry hashIndexEntry) {
|
||||
this.blockTransaction = blockTransaction;
|
||||
this.hashIndexEntry = hashIndexEntry;
|
||||
}
|
||||
|
||||
public BlockTransaction getBlockTransaction() {
|
||||
return blockTransaction;
|
||||
}
|
||||
|
||||
public HashIndexEntry getHashIndexEntry() {
|
||||
return hashIndexEntry;
|
||||
}
|
||||
}
|
|
@ -26,6 +26,7 @@ public class FontAwesome5 extends GlyphFont {
|
|||
LOCK_OPEN('\uf3c1'),
|
||||
QUESTION_CIRCLE('\uf059'),
|
||||
SD_CARD('\uf7c2'),
|
||||
SEARCH('\uf002'),
|
||||
WALLET('\uf555');
|
||||
|
||||
private final char ch;
|
||||
|
|
|
@ -32,5 +32,5 @@ public abstract class Entry {
|
|||
return children;
|
||||
}
|
||||
|
||||
public abstract Long getAmount();
|
||||
public abstract Long getValue();
|
||||
}
|
||||
|
|
|
@ -0,0 +1,64 @@
|
|||
package com.sparrowwallet.sparrow.wallet;
|
||||
|
||||
import com.sparrowwallet.drongo.wallet.BlockTransaction;
|
||||
import com.sparrowwallet.drongo.wallet.BlockTransactionHashIndex;
|
||||
import com.sparrowwallet.drongo.wallet.Wallet;
|
||||
import com.sparrowwallet.sparrow.EventManager;
|
||||
import com.sparrowwallet.sparrow.control.DateLabel;
|
||||
import com.sparrowwallet.sparrow.event.WalletChangedEvent;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
public class HashIndexEntry extends Entry {
|
||||
private final Wallet wallet;
|
||||
private final BlockTransactionHashIndex hashIndex;
|
||||
private final Type type;
|
||||
|
||||
public HashIndexEntry(Wallet wallet, BlockTransactionHashIndex hashIndex, Type type) {
|
||||
super(hashIndex.getLabel(), hashIndex.getSpentBy() != null ? List.of(new HashIndexEntry(wallet, hashIndex.getSpentBy(), Type.INPUT)) : Collections.emptyList());
|
||||
this.wallet = wallet;
|
||||
this.hashIndex = hashIndex;
|
||||
this.type = type;
|
||||
|
||||
labelProperty().addListener((observable, oldValue, newValue) -> {
|
||||
hashIndex.setLabel(newValue);
|
||||
EventManager.get().post(new WalletChangedEvent(wallet));
|
||||
});
|
||||
}
|
||||
|
||||
public Wallet getWallet() {
|
||||
return wallet;
|
||||
}
|
||||
|
||||
public BlockTransactionHashIndex getHashIndex() {
|
||||
return hashIndex;
|
||||
}
|
||||
|
||||
public Type getType() {
|
||||
return type;
|
||||
}
|
||||
|
||||
public BlockTransaction getBlockTransaction() {
|
||||
return wallet.getTransactions().get(hashIndex.getHash());
|
||||
}
|
||||
|
||||
public String getDescription() {
|
||||
return (type.equals(Type.INPUT) ? "Spent by " : "Received from ") +
|
||||
getHashIndex().getHash().toString().substring(0, 8) + "...:" + getHashIndex().getIndex() +
|
||||
" on " + DateLabel.getShortDateFormat(getHashIndex().getDate());
|
||||
}
|
||||
|
||||
public boolean isSpent() {
|
||||
return getType().equals(HashIndexEntry.Type.INPUT) || getHashIndex().getSpentBy() != null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Long getValue() {
|
||||
return hashIndex.getValue();
|
||||
}
|
||||
|
||||
public enum Type {
|
||||
INPUT, OUTPUT
|
||||
}
|
||||
}
|
|
@ -14,7 +14,11 @@ public class NodeEntry extends Entry {
|
|||
private final WalletNode node;
|
||||
|
||||
public NodeEntry(Wallet wallet, WalletNode node) {
|
||||
super(node.getLabel(), node.getChildren().stream().map(childNode -> new NodeEntry(wallet, childNode)).collect(Collectors.toList()));
|
||||
super(node.getLabel(),
|
||||
!node.getChildren().isEmpty() ?
|
||||
node.getChildren().stream().map(childNode -> new NodeEntry(wallet, childNode)).collect(Collectors.toList()) :
|
||||
node.getTransactionOutputs().stream().map(txo -> new HashIndexEntry(wallet, txo, HashIndexEntry.Type.OUTPUT)).collect(Collectors.toList()));
|
||||
|
||||
this.wallet = wallet;
|
||||
this.node = node;
|
||||
|
||||
|
@ -45,9 +49,11 @@ public class NodeEntry extends Entry {
|
|||
}
|
||||
|
||||
@Override
|
||||
public Long getAmount() {
|
||||
//TODO: Iterate through TransactionEntries to calculate amount
|
||||
|
||||
public Long getValue() {
|
||||
if(node.getTransactionOutputs().isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return node.getUnspentValue();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -8,10 +8,30 @@
|
|||
-fx-font-family: Courier;
|
||||
}
|
||||
|
||||
.hashindex-row {
|
||||
-fx-background-color: #fafafa;
|
||||
}
|
||||
|
||||
.hashindex-row .text {
|
||||
-fx-fill: #696c77;
|
||||
}
|
||||
|
||||
.hashindex-row.spent .text {
|
||||
-fx-fill: #a0a1a7;
|
||||
}
|
||||
|
||||
.label-cell .text-field {
|
||||
-fx-padding: 0;
|
||||
}
|
||||
|
||||
.amount-cell {
|
||||
-fx-alignment: center-right;
|
||||
}
|
||||
|
||||
.amount-cell.spent .text {
|
||||
-fx-strikethrough: true;
|
||||
}
|
||||
|
||||
.action-cell .button {
|
||||
-fx-padding: 0;
|
||||
-fx-pref-height: 18;
|
||||
|
|
Loading…
Reference in a new issue