From c9535e996705b3ed436975f95120f47550ddd153 Mon Sep 17 00:00:00 2001 From: woodser <13068859+woodser@users.noreply.github.com> Date: Tue, 28 Oct 2025 14:40:09 -0400 Subject: [PATCH] add filter box to transactions view --- .../desktop/components/list/FilterBox.java | 18 ++++++- .../transactions/DisplayedTransactions.java | 7 +-- .../transactions/TransactionsListItem.java | 32 +++++++++++- .../funds/transactions/TransactionsView.fxml | 2 + .../funds/transactions/TransactionsView.java | 50 ++++++++++++------- 5 files changed, 82 insertions(+), 27 deletions(-) diff --git a/desktop/src/main/java/haveno/desktop/components/list/FilterBox.java b/desktop/src/main/java/haveno/desktop/components/list/FilterBox.java index 017e22fac2..7b403b697b 100644 --- a/desktop/src/main/java/haveno/desktop/components/list/FilterBox.java +++ b/desktop/src/main/java/haveno/desktop/components/list/FilterBox.java @@ -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 filteredList, + TableView 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 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)); } diff --git a/desktop/src/main/java/haveno/desktop/main/funds/transactions/DisplayedTransactions.java b/desktop/src/main/java/haveno/desktop/main/funds/transactions/DisplayedTransactions.java index 4400bfbadd..94bc50f4a0 100644 --- a/desktop/src/main/java/haveno/desktop/main/funds/transactions/DisplayedTransactions.java +++ b/desktop/src/main/java/haveno/desktop/main/funds/transactions/DisplayedTransactions.java @@ -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 transactionsListItems = getTransactionListItems(); - UserThread.execute(() -> { - forEach(TransactionsListItem::cleanup); - setAll(transactionsListItems); - }); + forEach(TransactionsListItem::cleanup); + setAll(transactionsListItems); } private List getTransactionListItems() { diff --git a/desktop/src/main/java/haveno/desktop/main/funds/transactions/TransactionsListItem.java b/desktop/src/main/java/haveno/desktop/main/funds/transactions/TransactionsListItem.java index 930d8f9572..2ae6fa0178 100644 --- a/desktop/src/main/java/haveno/desktop/main/funds/transactions/TransactionsListItem.java +++ b/desktop/src/main/java/haveno/desktop/main/funds/transactions/TransactionsListItem.java @@ -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); + } } diff --git a/desktop/src/main/java/haveno/desktop/main/funds/transactions/TransactionsView.fxml b/desktop/src/main/java/haveno/desktop/main/funds/transactions/TransactionsView.fxml index 15d2177129..09ab07b095 100644 --- a/desktop/src/main/java/haveno/desktop/main/funds/transactions/TransactionsView.fxml +++ b/desktop/src/main/java/haveno/desktop/main/funds/transactions/TransactionsView.fxml @@ -25,11 +25,13 @@ + + diff --git a/desktop/src/main/java/haveno/desktop/main/funds/transactions/TransactionsView.java b/desktop/src/main/java/haveno/desktop/main/funds/transactions/TransactionsView.java index 8e1e999f30..9633d8682c 100644 --- a/desktop/src/main/java/haveno/desktop/main/funds/transactions/TransactionsView.java +++ b/desktop/src/main/java/haveno/desktop/main/funds/transactions/TransactionsView.java @@ -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 { - + @FXML + FilterBox filterBox; @FXML TableView tableView; @FXML @@ -76,7 +81,10 @@ public class TransactionsView extends ActivatableView { AutoTooltipButton exportButton; private final DisplayedTransactions displayedTransactions; - private final SortedList sortedDisplayedTransactions; + + private final ObservableList observableList = FXCollections.observableArrayList(); + private final FilteredList filteredList = new FilteredList<>(observableList); + private final SortedList sortedList = new SortedList<>(filteredList); private final XmrWalletService xmrWalletService; private final Preferences preferences; @@ -92,11 +100,11 @@ public class TransactionsView extends ActivatableView { 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 { 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 { @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 { if (scene != null) scene.addEventHandler(KeyEvent.KEY_RELEASED, keyEventEventHandler); - numItems.setText(Res.get("shared.numItemsLabel", sortedDisplayedTransactions.size())); exportButton.setOnAction(event -> { final ObservableList> tableColumns = GUIUtil.getContentColumns(tableView); final int reportColumns = tableColumns.size(); @@ -217,14 +222,14 @@ public class TransactionsView extends ActivatableView { }; 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 { 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);