refactor filtering for failed trade list item (#1889)

This commit is contained in:
woodser 2025-07-30 07:55:49 -04:00 committed by GitHub
parent d20ad82a9f
commit f96f7d2b96
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
7 changed files with 150 additions and 157 deletions

View file

@ -52,7 +52,7 @@ public class PortfolioView extends ActivatableView<TabPane, Void> {
@FXML
Tab openOffersTab, pendingTradesTab, closedTradesTab;
private Tab editOpenOfferTab, duplicateOfferTab, cloneOpenOfferTab;
private final Tab failedTradesTab = new Tab(Res.get("portfolio.tab.failed").toUpperCase());
private final Tab failedTradesTab = new Tab(Res.get("portfolio.tab.failed"));
private Tab currentTab;
private Navigation.Listener navigationListener;
private ChangeListener<Tab> tabChangeListener;

View file

@ -68,7 +68,11 @@ class FailedTradesDataModel extends ActivatableDataModel {
private void applyList() {
list.clear();
list.addAll(failedTradesManager.getObservableList().stream().map(FailedTradesListItem::new).collect(Collectors.toList()));
list.addAll(
failedTradesManager.getObservableList().stream()
.map(trade -> new FailedTradesListItem(trade, failedTradesManager))
.collect(Collectors.toList())
);
// we sort by date, earliest first
list.sort((o1, o2) -> o2.getTrade().getDate().compareTo(o1.getTrade().getDate()));

View file

@ -17,18 +17,117 @@
package haveno.desktop.main.portfolio.failedtrades;
import org.apache.commons.lang3.StringUtils;
import haveno.core.locale.CurrencyUtil;
import haveno.core.locale.Res;
import haveno.core.offer.Offer;
import haveno.core.offer.OfferDirection;
import haveno.core.trade.ArbitratorTrade;
import haveno.core.trade.HavenoUtils;
import haveno.core.trade.Trade;
import haveno.core.trade.failed.FailedTradesManager;
import haveno.core.util.FormattingUtils;
import haveno.core.util.VolumeUtil;
import haveno.desktop.util.DisplayUtils;
import haveno.desktop.util.filtering.FilterableListItem;
import haveno.desktop.util.filtering.FilteringUtils;
import lombok.Getter;
class FailedTradesListItem {
class FailedTradesListItem implements FilterableListItem {
@Getter
private final Trade trade;
private final FailedTradesManager failedTradesManager;
FailedTradesListItem(Trade trade) {
FailedTradesListItem(Trade trade,
FailedTradesManager failedTradesManager) {
this.trade = trade;
this.failedTradesManager = failedTradesManager;
}
FailedTradesListItem() {
this.trade = null;
this.failedTradesManager = null;
}
public String getDateAsString() {
return DisplayUtils.formatDateTime(trade.getDate());
}
public String getPriceAsString() {
return FormattingUtils.formatPrice(trade.getPrice());
}
public String getAmountAsString() {
return HavenoUtils.formatXmr(trade.getAmount());
}
public String getPaymentMethod() {
return trade.getOffer().getPaymentMethodNameWithCountryCode();
}
public String getMarketDescription() {
return CurrencyUtil.getCurrencyPair(trade.getOffer().getCounterCurrencyCode());
}
public String getDirectionLabel() {
Offer offer = trade.getOffer();
OfferDirection direction = failedTradesManager.wasMyOffer(offer) || trade instanceof ArbitratorTrade
? offer.getDirection()
: offer.getMirroredDirection();
String currencyCode = trade.getOffer().getCounterCurrencyCode();
return DisplayUtils.getDirectionWithCode(direction, currencyCode, offer.isPrivateOffer());
}
public String getVolumeAsString() {
return VolumeUtil.formatVolumeWithCode(trade.getVolume());
}
public String getState() {
return Res.get("portfolio.failed.Failed");
}
@Override
public boolean match(String filterString) {
if (filterString.isEmpty()) {
return true;
}
if (StringUtils.containsIgnoreCase(getDateAsString(), filterString)) {
return true;
}
if (StringUtils.containsIgnoreCase(getMarketDescription(), filterString)) {
return true;
}
if (StringUtils.containsIgnoreCase(getPriceAsString(), filterString)) {
return true;
}
if (StringUtils.containsIgnoreCase(getPaymentMethod(), filterString)) {
return true;
}
if (StringUtils.containsIgnoreCase(getAmountAsString(), filterString)) {
return true;
}
if (StringUtils.containsIgnoreCase(getDirectionLabel(), filterString)) {
return true;
}
if (StringUtils.containsIgnoreCase(getVolumeAsString(), filterString)) {
return true;
}
if (StringUtils.containsIgnoreCase(getState(), filterString)) {
return true;
}
if (StringUtils.containsIgnoreCase(getTrade().getOffer().getCombinedExtraInfo(), filterString)) {
return true;
}
if (trade.getBuyer().getPaymentAccountPayload() != null && StringUtils.containsIgnoreCase(getTrade().getBuyer().getPaymentAccountPayload().getPaymentDetails(), filterString)) {
return true;
}
if (trade.getSeller().getPaymentAccountPayload() != null && StringUtils.containsIgnoreCase(getTrade().getSeller().getPaymentAccountPayload().getPaymentDetails(), filterString)) {
return true;
}
if (FilteringUtils.match(trade.getOffer(), filterString)) {
return true;
}
return FilteringUtils.match(trade, filterString);
}
}

View file

@ -17,6 +17,7 @@
~ along with Haveno. If not, see <http://www.gnu.org/licenses/>.
-->
<?import haveno.desktop.components.list.FilterBox?>
<?import haveno.desktop.components.AutoTooltipButton?>
<?import haveno.desktop.components.AutoTooltipLabel?>
<?import haveno.desktop.components.InputTextField?>
@ -33,11 +34,8 @@
<padding>
<Insets bottom="15.0" left="15.0" right="15.0" top="15.0"/>
</padding>
<HBox fx:id="searchBox">
<AutoTooltipLabel fx:id="filterLabel"/>
<InputTextField fx:id="filterTextField" minWidth="500"/>
<Pane fx:id="searchBoxSpacer"/>
</HBox>
<FilterBox fx:id="filterBox" />
<TableView fx:id="tableView" VBox.vgrow="ALWAYS">
<columns>
<TableColumn fx:id="tradeIdColumn" minWidth="120" maxWidth="120"/>

View file

@ -23,8 +23,6 @@ import com.jfoenix.controls.JFXButton;
import de.jensd.fx.fontawesome.AwesomeIcon;
import haveno.common.util.Utilities;
import haveno.core.locale.Res;
import haveno.core.offer.Offer;
import haveno.core.trade.Contract;
import haveno.core.trade.HavenoUtils;
import haveno.core.trade.Trade;
import haveno.core.user.User;
@ -34,7 +32,7 @@ import haveno.desktop.common.view.FxmlView;
import haveno.desktop.components.AutoTooltipButton;
import haveno.desktop.components.AutoTooltipLabel;
import haveno.desktop.components.HyperlinkWithIcon;
import haveno.desktop.components.InputTextField;
import haveno.desktop.components.list.FilterBox;
import haveno.desktop.main.offer.OfferViewUtil;
import haveno.desktop.main.overlays.popups.Popup;
import haveno.desktop.main.overlays.windows.TradeDetailsWindow;
@ -43,7 +41,6 @@ import haveno.desktop.util.GUIUtil;
import java.math.BigInteger;
import java.util.Comparator;
import javafx.beans.property.ReadOnlyObjectWrapper;
import javafx.beans.value.ChangeListener;
import javafx.collections.ObservableList;
import javafx.collections.transformation.FilteredList;
import javafx.collections.transformation.SortedList;
@ -62,7 +59,6 @@ import javafx.scene.control.Tooltip;
import javafx.scene.input.KeyCode;
import javafx.scene.input.KeyEvent;
import javafx.scene.layout.HBox;
import javafx.scene.layout.Pane;
import javafx.scene.layout.Priority;
import javafx.scene.layout.Region;
import javafx.scene.layout.VBox;
@ -78,13 +74,7 @@ public class FailedTradesView extends ActivatableViewAndModel<VBox, FailedTrades
TableColumn<FailedTradesListItem, FailedTradesListItem> priceColumn, amountColumn, volumeColumn,
marketColumn, directionColumn, dateColumn, tradeIdColumn, stateColumn, removeTradeColumn;
@FXML
HBox searchBox;
@FXML
AutoTooltipLabel filterLabel;
@FXML
InputTextField filterTextField;
@FXML
Pane searchBoxSpacer;
FilterBox filterBox;
@FXML
Label numItems;
@FXML
@ -96,7 +86,6 @@ public class FailedTradesView extends ActivatableViewAndModel<VBox, FailedTrades
private SortedList<FailedTradesListItem> sortedList;
private FilteredList<FailedTradesListItem> filteredList;
private EventHandler<KeyEvent> keyEventEventHandler;
private ChangeListener<String> filterTextFieldListener;
private Scene scene;
private XmrWalletService xmrWalletService;
private User user;
@ -144,8 +133,8 @@ public class FailedTradesView extends ActivatableViewAndModel<VBox, FailedTrades
priceColumn.setComparator(Comparator.comparing(o -> o.getTrade().getPrice()));
volumeColumn.setComparator(Comparator.comparing(o -> o.getTrade().getVolume(), Comparator.nullsFirst(Comparator.naturalOrder())));
amountColumn.setComparator(Comparator.comparing(o -> o.getTrade().getAmount(), Comparator.nullsFirst(Comparator.naturalOrder())));
stateColumn.setComparator(Comparator.comparing(model::getState));
marketColumn.setComparator(Comparator.comparing(model::getMarketLabel));
stateColumn.setComparator(Comparator.comparing(o -> o.getState()));
marketColumn.setComparator(Comparator.comparing(o -> o.getMarketDescription()));
dateColumn.setSortType(TableColumn.SortType.DESCENDING);
tableView.getSortOrder().add(dateColumn);
@ -170,12 +159,6 @@ public class FailedTradesView extends ActivatableViewAndModel<VBox, FailedTrades
}
};
filterLabel.setText(Res.get("shared.filter"));
HBox.setMargin(filterLabel, new Insets(5, 0, 0, 10));
filterTextFieldListener = (observable, oldValue, newValue) -> applyFilteredListPredicate(filterTextField.getText());
searchBox.setSpacing(5);
HBox.setHgrow(searchBoxSpacer, Priority.ALWAYS);
numItems.setId("num-offers");
numItems.setPadding(new Insets(-5, 0, 0, 10));
HBox.setHgrow(footerSpacer, Priority.ALWAYS);
@ -195,6 +178,10 @@ public class FailedTradesView extends ActivatableViewAndModel<VBox, FailedTrades
sortedList.comparatorProperty().bind(tableView.comparatorProperty());
tableView.setItems(sortedList);
filterBox.initialize(filteredList, tableView); // here because filteredList is instantiated here
filterBox.setPromptText(Res.get("shared.filter"));
filterBox.activate();
contextMenu = new ContextMenu();
boolean isArbitrator = user.getRegisteredArbitrator() != null;
if (isArbitrator) {
@ -234,7 +221,7 @@ public class FailedTradesView extends ActivatableViewAndModel<VBox, FailedTrades
numItems.setText(Res.get("shared.numItemsLabel", sortedList.size()));
exportButton.setOnAction(event -> {
ObservableList<TableColumn<FailedTradesListItem, ?>> tableColumns = tableView.getColumns();
ObservableList<TableColumn<FailedTradesListItem, ?>> tableColumns = GUIUtil.getContentColumns(tableView);
int reportColumns = tableColumns.size() - 1; // CSV report excludes the last column (an icon)
CSVEntryConverter<FailedTradesListItem> headerConverter = item -> {
String[] columns = new String[reportColumns];
@ -244,14 +231,14 @@ public class FailedTradesView extends ActivatableViewAndModel<VBox, FailedTrades
};
CSVEntryConverter<FailedTradesListItem> contentConverter = item -> {
String[] columns = new String[reportColumns];
columns[0] = model.getTradeId(item);
columns[1] = model.getDate(item);
columns[2] = model.getMarketLabel(item);
columns[3] = model.getPrice(item);
columns[4] = model.getAmount(item);
columns[5] = model.getVolume(item);
columns[6] = model.getDirectionLabel(item);
columns[7] = model.getState(item);
columns[0] = item.getTrade().getId();
columns[1] = item.getDateAsString();
columns[2] = item.getMarketDescription();
columns[3] = item.getPriceAsString();
columns[4] = item.getAmountAsString();
columns[5] = item.getVolumeAsString();
columns[6] = item.getDirectionLabel();
columns[7] = item.getState();
return columns;
};
@ -262,9 +249,6 @@ public class FailedTradesView extends ActivatableViewAndModel<VBox, FailedTrades
sortedList,
(Stage) root.getScene().getWindow());
});
filterTextField.textProperty().addListener(filterTextFieldListener);
applyFilteredListPredicate(filterTextField.getText());
}
private void handleContextMenu(String msgKey, String buyerOrSeller, String makerOrTaker, BigInteger fee, String reserveTxHash, String reserveTxHex) {
@ -293,66 +277,9 @@ public class FailedTradesView extends ActivatableViewAndModel<VBox, FailedTrades
sortedList.comparatorProperty().unbind();
exportButton.setOnAction(null);
filterTextField.textProperty().removeListener(filterTextFieldListener);
filterBox.deactivate();
}
private void applyFilteredListPredicate(String filterString) {
filteredList.setPredicate(item -> {
if (filterString.isEmpty())
return true;
Offer offer = item.getTrade().getOffer();
if (offer.getId().contains(filterString)) {
return true;
}
if (model.getDate(item).contains(filterString)) {
return true;
}
if (model.getMarketLabel(item).contains(filterString)) {
return true;
}
if (model.getPrice(item).contains(filterString)) {
return true;
}
if (model.getVolume(item).contains(filterString)) {
return true;
}
if (model.getAmount(item).contains(filterString)) {
return true;
}
if (model.getDirectionLabel(item).contains(filterString)) {
return true;
}
Trade trade = item.getTrade();
if (trade.getMaker().getDepositTxHash() != null && trade.getMaker().getDepositTxHash().contains(filterString)) {
return true;
}
if (trade.getTaker().getDepositTxHash() != null && trade.getTaker().getDepositTxHash().contains(filterString)) {
return true;
}
if (trade.getPayoutTxId() != null && trade.getPayoutTxId().contains(filterString)) {
return true;
}
Contract contract = trade.getContract();
boolean isBuyerOnion = false;
boolean isSellerOnion = false;
boolean matchesBuyersPaymentAccountData = false;
boolean matchesSellersPaymentAccountData = false;
if (contract != null) {
isBuyerOnion = contract.getBuyerNodeAddress().getFullAddress().contains(filterString);
isSellerOnion = contract.getSellerNodeAddress().getFullAddress().contains(filterString);
matchesBuyersPaymentAccountData = trade.getBuyer().getPaymentAccountPayload().getPaymentDetails().contains(filterString);
matchesSellersPaymentAccountData = trade.getSeller().getPaymentAccountPayload().getPaymentDetails().contains(filterString);
}
return isBuyerOnion || isSellerOnion ||
matchesBuyersPaymentAccountData || matchesSellersPaymentAccountData;
});
}
private void onUnfail() {
Trade trade = sortedList.get(tableView.getSelectionModel().getFocusedIndex()).getTrade();
@ -401,7 +328,7 @@ public class FailedTradesView extends ActivatableViewAndModel<VBox, FailedTrades
public void updateItem(final FailedTradesListItem item, boolean empty) {
super.updateItem(item, empty);
if (item != null && !empty) {
field = new HyperlinkWithIcon(model.getTradeId(item));
field = new HyperlinkWithIcon(item.getTrade().getShortId());
field.setOnAction(event -> tradeDetailsWindow.show(item.getTrade()));
field.setTooltip(new Tooltip(Res.get("tooltip.openPopupForDetails")));
setGraphic(field);
@ -428,7 +355,7 @@ public class FailedTradesView extends ActivatableViewAndModel<VBox, FailedTrades
public void updateItem(final FailedTradesListItem item, boolean empty) {
super.updateItem(item, empty);
if (item != null)
setGraphic(new AutoTooltipLabel(model.getDate(item)));
setGraphic(new AutoTooltipLabel(item.getDateAsString()));
else
setGraphic(null);
}
@ -448,7 +375,10 @@ public class FailedTradesView extends ActivatableViewAndModel<VBox, FailedTrades
@Override
public void updateItem(final FailedTradesListItem item, boolean empty) {
super.updateItem(item, empty);
setGraphic(new AutoTooltipLabel(model.getMarketLabel(item)));
if (item != null)
setGraphic(new AutoTooltipLabel(item.getMarketDescription()));
else
setGraphic(null);
}
};
}
@ -467,7 +397,7 @@ public class FailedTradesView extends ActivatableViewAndModel<VBox, FailedTrades
public void updateItem(final FailedTradesListItem item, boolean empty) {
super.updateItem(item, empty);
if (item != null)
setGraphic(new AutoTooltipLabel(model.getState(item)));
setGraphic(new AutoTooltipLabel(item.getState()));
else
setGraphic(null);
}
@ -488,7 +418,10 @@ public class FailedTradesView extends ActivatableViewAndModel<VBox, FailedTrades
@Override
public void updateItem(final FailedTradesListItem item, boolean empty) {
super.updateItem(item, empty);
setGraphic(new AutoTooltipLabel(model.getAmount(item)));
if (item != null)
setGraphic(new AutoTooltipLabel(item.getAmountAsString()));
else
setGraphic(null);
}
};
}
@ -506,7 +439,10 @@ public class FailedTradesView extends ActivatableViewAndModel<VBox, FailedTrades
@Override
public void updateItem(final FailedTradesListItem item, boolean empty) {
super.updateItem(item, empty);
setGraphic(new AutoTooltipLabel(model.getPrice(item)));
if (item != null)
setGraphic(new AutoTooltipLabel(item.getPriceAsString()));
else
setGraphic(null);
}
};
}
@ -525,7 +461,7 @@ public class FailedTradesView extends ActivatableViewAndModel<VBox, FailedTrades
public void updateItem(final FailedTradesListItem item, boolean empty) {
super.updateItem(item, empty);
if (item != null)
setGraphic(new AutoTooltipLabel(model.getVolume(item)));
setGraphic(new AutoTooltipLabel(item.getVolumeAsString()));
else
setGraphic(null);
}
@ -545,7 +481,10 @@ public class FailedTradesView extends ActivatableViewAndModel<VBox, FailedTrades
@Override
public void updateItem(final FailedTradesListItem item, boolean empty) {
super.updateItem(item, empty);
setGraphic(new AutoTooltipLabel(model.getDirectionLabel(item)));
if (item != null)
setGraphic(new AutoTooltipLabel(item.getDirectionLabel()));
else
setGraphic(null);
}
};
}

View file

@ -19,15 +19,10 @@ package haveno.desktop.main.portfolio.failedtrades;
import com.google.inject.Inject;
import com.google.inject.name.Named;
import haveno.core.locale.CurrencyUtil;
import haveno.core.locale.Res;
import haveno.core.trade.HavenoUtils;
import haveno.core.util.FormattingUtils;
import haveno.core.util.VolumeUtil;
import haveno.core.util.coin.CoinFormatter;
import haveno.desktop.common.model.ActivatableWithDataModel;
import haveno.desktop.common.model.ViewModel;
import haveno.desktop.util.DisplayUtils;
import javafx.collections.ObservableList;
class FailedTradesViewModel extends ActivatableWithDataModel<FailedTradesDataModel> implements ViewModel {
@ -44,45 +39,4 @@ class FailedTradesViewModel extends ActivatableWithDataModel<FailedTradesDataMod
public ObservableList<FailedTradesListItem> getList() {
return dataModel.getList();
}
String getTradeId(FailedTradesListItem item) {
return item.getTrade().getShortId();
}
String getAmount(FailedTradesListItem item) {
if (item != null && item.getTrade() != null)
return HavenoUtils.formatXmr(item.getTrade().getAmount());
else
return "";
}
String getPrice(FailedTradesListItem item) {
return (item != null) ? FormattingUtils.formatPrice(item.getTrade().getPrice()) : "";
}
String getVolume(FailedTradesListItem item) {
if (item != null && item.getTrade() != null)
return VolumeUtil.formatVolumeWithCode(item.getTrade().getVolume());
else
return "";
}
String getDirectionLabel(FailedTradesListItem item) {
return (item != null) ? DisplayUtils.getDirectionWithCode(dataModel.getDirection(item.getTrade().getOffer()), item.getTrade().getOffer().getCounterCurrencyCode(), item.getTrade().getOffer().isPrivateOffer()) : "";
}
String getMarketLabel(FailedTradesListItem item) {
if ((item == null))
return "";
return CurrencyUtil.getCurrencyPair(item.getTrade().getOffer().getCounterCurrencyCode());
}
String getDate(FailedTradesListItem item) {
return DisplayUtils.formatDateTime(item.getTrade().getDate());
}
String getState(FailedTradesListItem item) {
return item != null ? Res.get("portfolio.failed.Failed") : "";
}
}

View file

@ -17,6 +17,7 @@
package haveno.desktop.main.portfolio.pendingtrades;
import haveno.core.locale.CurrencyUtil;
import haveno.core.monetary.Price;
import haveno.core.trade.HavenoUtils;
import haveno.core.trade.Trade;
@ -27,8 +28,6 @@ import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import static haveno.core.locale.CurrencyUtil.getCurrencyPair;
/**
* We could remove that wrapper if it is not needed for additional UI only fields.
*/
@ -63,7 +62,7 @@ public class PendingTradesListItem implements FilterableListItem {
}
public String getMarketDescription() {
return getCurrencyPair(trade.getOffer().getCounterCurrencyCode());
return CurrencyUtil.getCurrencyPair(trade.getOffer().getCounterCurrencyCode());
}
@Override