add filter box to transactions view

This commit is contained in:
woodser 2025-10-28 14:40:09 -04:00 committed by woodser
parent 26982294bb
commit c9535e9967
5 changed files with 82 additions and 27 deletions

View file

@ -46,12 +46,18 @@ public class FilterBox extends HBox {
this.filteredList = filteredList;
listener = (observable, oldValue, newValue) -> {
UserThread.execute(() -> {
tableView.getSelectionModel().clearSelection();
applyFilteredListPredicate(textField.getText());
applyFilter(tableView, null);
});
};
}
public void initializeWithCallback(FilteredList<? extends FilterableListItem> filteredList,
TableView<? extends FilterableListItem> tableView, Runnable callback) {
this.filteredList = filteredList;
listener = (observable, oldValue, newValue) -> applyFilter(tableView, callback);
applyFilter(tableView, callback); // first time init
}
public void activate() {
textField.textProperty().addListener(listener);
applyFilteredListPredicate(textField.getText());
@ -61,6 +67,14 @@ public class FilterBox extends HBox {
textField.textProperty().removeListener(listener);
}
private void applyFilter(TableView<? extends FilterableListItem> tableView, Runnable callback) {
tableView.getSelectionModel().clearSelection();
applyFilteredListPredicate(textField.getText());
if (callback != null) {
callback.run();
}
}
private void applyFilteredListPredicate(String filterString) {
filteredList.setPredicate(item -> item.match(filterString));
}

View file

@ -17,7 +17,6 @@
package haveno.desktop.main.funds.transactions;
import haveno.common.UserThread;
import haveno.core.trade.Tradable;
import haveno.core.xmr.wallet.XmrWalletService;
import monero.wallet.model.MoneroTxWallet;
@ -43,10 +42,8 @@ class DisplayedTransactions extends ObservableListDecorator<TransactionsListItem
void update() {
List<TransactionsListItem> transactionsListItems = getTransactionListItems();
UserThread.execute(() -> {
forEach(TransactionsListItem::cleanup);
setAll(transactionsListItems);
});
forEach(TransactionsListItem::cleanup);
setAll(transactionsListItems);
}
private List<TransactionsListItem> getTransactionListItems() {

View file

@ -30,6 +30,7 @@ import haveno.core.xmr.wallet.XmrWalletService;
import haveno.desktop.components.indicator.TxConfidenceIndicator;
import haveno.desktop.util.DisplayUtils;
import haveno.desktop.util.GUIUtil;
import haveno.desktop.util.filtering.FilterableListItem;
import javafx.scene.control.Tooltip;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
@ -38,12 +39,15 @@ import monero.wallet.model.MoneroOutgoingTransfer;
import monero.wallet.model.MoneroTxWallet;
import javax.annotation.Nullable;
import org.apache.commons.lang3.StringUtils;
import java.math.BigInteger;
import java.util.Date;
import java.util.Optional;
@Slf4j
public class TransactionsListItem {
public class TransactionsListItem implements FilterableListItem {
private String dateString;
private final Date date;
private final String txId;
@ -258,4 +262,30 @@ public class TransactionsListItem {
public String getMemo() {
return memo;
}
@Override
public boolean match(String filterString) {
if (filterString.isEmpty()) {
return true;
}
if (StringUtils.containsIgnoreCase(getTxId(), filterString)) {
return true;
}
if (StringUtils.containsIgnoreCase(getDetails(), filterString)) {
return true;
}
if (getMemo() != null && StringUtils.containsIgnoreCase(getMemo(), filterString)) {
return true;
}
if (StringUtils.containsIgnoreCase(getDirection(), filterString)) {
return true;
}
if (StringUtils.containsIgnoreCase(getDateString(), filterString)) {
return true;
}
if (StringUtils.containsIgnoreCase(getAmountStr(), filterString)) {
return true;
}
return StringUtils.containsIgnoreCase(getAddressString(), filterString);
}
}

View file

@ -25,11 +25,13 @@
<?import javafx.scene.layout.HBox?>
<?import javafx.scene.layout.Region?>
<?import javafx.scene.layout.VBox?>
<?import haveno.desktop.components.list.FilterBox?>
<VBox fx:id="root" fx:controller="haveno.desktop.main.funds.transactions.TransactionsView"
spacing="10" alignment="CENTER_RIGHT" xmlns:fx="http://javafx.com/fxml">
<padding>
<Insets bottom="15.0" left="15.0" right="15.0" top="15.0"/>
</padding>
<FilterBox fx:id="filterBox" />
<TableView fx:id="tableView" VBox.vgrow="ALWAYS">
<columns>
<TableColumn fx:id="dateColumn" minWidth="100" maxWidth="180"/>

View file

@ -20,6 +20,7 @@ package haveno.desktop.main.funds.transactions;
import com.google.inject.Inject;
import com.googlecode.jcsv.writer.CSVEntryConverter;
import de.jensd.fx.fontawesome.AwesomeIcon;
import haveno.common.UserThread;
import haveno.core.api.XmrConnectionService;
import haveno.core.locale.Res;
import haveno.core.offer.OpenOffer;
@ -32,6 +33,7 @@ import haveno.desktop.components.AddressWithIconAndDirection;
import haveno.desktop.components.AutoTooltipButton;
import haveno.desktop.components.AutoTooltipLabel;
import haveno.desktop.components.HyperlinkWithIcon;
import haveno.desktop.components.list.FilterBox;
import haveno.desktop.main.overlays.windows.OfferDetailsWindow;
import haveno.desktop.main.overlays.windows.TradeDetailsWindow;
import haveno.desktop.main.overlays.windows.TxDetailsWindow;
@ -40,7 +42,9 @@ import haveno.network.p2p.P2PService;
import java.math.BigInteger;
import java.util.Comparator;
import javafx.beans.property.ReadOnlyObjectWrapper;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.collections.transformation.FilteredList;
import javafx.collections.transformation.SortedList;
import javafx.event.EventHandler;
import javafx.fxml.FXML;
@ -63,7 +67,8 @@ import monero.wallet.model.MoneroWalletListener;
@FxmlView
public class TransactionsView extends ActivatableView<VBox, Void> {
@FXML
FilterBox filterBox;
@FXML
TableView<TransactionsListItem> tableView;
@FXML
@ -76,7 +81,10 @@ public class TransactionsView extends ActivatableView<VBox, Void> {
AutoTooltipButton exportButton;
private final DisplayedTransactions displayedTransactions;
private final SortedList<TransactionsListItem> sortedDisplayedTransactions;
private final ObservableList<TransactionsListItem> observableList = FXCollections.observableArrayList();
private final FilteredList<TransactionsListItem> filteredList = new FilteredList<>(observableList);
private final SortedList<TransactionsListItem> sortedList = new SortedList<>(filteredList);
private final XmrWalletService xmrWalletService;
private final Preferences preferences;
@ -92,11 +100,11 @@ public class TransactionsView extends ActivatableView<VBox, Void> {
private class TransactionsUpdater extends MoneroWalletListener {
@Override
public void onNewBlock(long height) {
displayedTransactions.update();
updateList();
}
@Override
public void onBalancesChanged(BigInteger newBalance, BigInteger newUnlockedBalance) {
displayedTransactions.update();
updateList();
}
}
@ -119,12 +127,14 @@ public class TransactionsView extends ActivatableView<VBox, Void> {
this.offerDetailsWindow = offerDetailsWindow;
this.txDetailsWindow = txDetailsWindow;
this.displayedTransactions = displayedTransactionsFactory.create();
this.sortedDisplayedTransactions = displayedTransactions.asSortedList();
updateList();
}
@Override
public void initialize() {
GUIUtil.applyTableStyle(tableView);
filterBox.initialize(filteredList, tableView);
filterBox.setPromptText(Res.get("shared.filter"));
dateColumn.setGraphic(new AutoTooltipLabel(Res.get("shared.dateTime")));
detailsColumn.setGraphic(new AutoTooltipLabel(Res.get("shared.details")));
@ -176,16 +186,12 @@ public class TransactionsView extends ActivatableView<VBox, Void> {
@Override
protected void activate() {
sortedDisplayedTransactions.comparatorProperty().bind(tableView.comparatorProperty());
tableView.setItems(sortedDisplayedTransactions);
// try to update displayed transactions
try {
displayedTransactions.update();
} catch (Exception e) {
log.warn("Failed to update displayed transactions");
e.printStackTrace();
}
sortedList.comparatorProperty().bind(tableView.comparatorProperty());
tableView.setItems(sortedList);
updateList();
filterBox.initializeWithCallback(filteredList, tableView, () ->
numItems.setText(Res.get("shared.numItemsLabel", sortedList.size())));
filterBox.activate();
xmrWalletService.addWalletListener(transactionsUpdater);
@ -193,7 +199,6 @@ public class TransactionsView extends ActivatableView<VBox, Void> {
if (scene != null)
scene.addEventHandler(KeyEvent.KEY_RELEASED, keyEventEventHandler);
numItems.setText(Res.get("shared.numItemsLabel", sortedDisplayedTransactions.size()));
exportButton.setOnAction(event -> {
final ObservableList<TableColumn<TransactionsListItem, ?>> tableColumns = GUIUtil.getContentColumns(tableView);
final int reportColumns = tableColumns.size();
@ -217,14 +222,14 @@ public class TransactionsView extends ActivatableView<VBox, Void> {
};
GUIUtil.exportCSV("transactions.csv", headerConverter, contentConverter,
new TransactionsListItem(), sortedDisplayedTransactions, (Stage) root.getScene().getWindow());
new TransactionsListItem(), sortedList, (Stage) root.getScene().getWindow());
});
}
@Override
protected void deactivate() {
sortedDisplayedTransactions.comparatorProperty().unbind();
displayedTransactions.forEach(TransactionsListItem::cleanup);
filterBox.deactivate();
sortedList.comparatorProperty().unbind();
xmrWalletService.removeWalletListener(transactionsUpdater);
if (scene != null)
@ -233,6 +238,13 @@ public class TransactionsView extends ActivatableView<VBox, Void> {
exportButton.setOnAction(null);
}
private void updateList() {
displayedTransactions.update();
UserThread.execute((() -> {
observableList.setAll(displayedTransactions);
}));
}
private void openTxInBlockExplorer(TransactionsListItem item) {
if (item.getTxId() != null)
GUIUtil.openWebPage(preferences.getBlockChainExplorer().txUrl + item.getTxId(), false);